WIP: eevee-next-world-irradiance #108304

Closed
Jeroen Bakker wants to merge 79 commits from Jeroen-Bakker:eevee-next-world-irradiance into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
22 changed files with 435 additions and 13 deletions

View File

@ -154,6 +154,7 @@ set(SRC
engines/eevee_next/eevee_material.cc
engines/eevee_next/eevee_motion_blur.cc
engines/eevee_next/eevee_pipeline.cc
engines/eevee_next/eevee_reflection_probes.cc
engines/eevee_next/eevee_renderbuffers.cc
engines/eevee_next/eevee_sampling.cc
engines/eevee_next/eevee_shader.cc
@ -294,6 +295,7 @@ set(SRC
engines/eevee_next/eevee_material.hh
engines/eevee_next/eevee_motion_blur.hh
engines/eevee_next/eevee_pipeline.hh
engines/eevee_next/eevee_reflection_probes.hh
engines/eevee_next/eevee_renderbuffers.hh
engines/eevee_next/eevee_sampling.hh
engines/eevee_next/eevee_shader.hh
@ -508,6 +510,7 @@ set(GLSL_SRC
engines/eevee_next/shaders/eevee_motion_blur_gather_comp.glsl
engines/eevee_next/shaders/eevee_motion_blur_lib.glsl
engines/eevee_next/shaders/eevee_nodetree_lib.glsl
engines/eevee_next/shaders/eevee_reflection_probe_lib.glsl
engines/eevee_next/shaders/eevee_sampling_lib.glsl
engines/eevee_next/shaders/eevee_shadow_debug_frag.glsl
engines/eevee_next/shaders/eevee_shadow_lib.glsl

View File

@ -108,6 +108,7 @@
#define SHADOW_ATLAS_TEX_SLOT 5
#define SSS_TRANSMITTANCE_TEX_SLOT 6
#define IRRADIANCE_ATLAS_TEX_SLOT 7
#define REFLECTION_PROBE_TEX_SLOT 8
/* Only during shadow rendering. */
#define SHADOW_RENDER_MAP_SLOT 4

View File

@ -70,6 +70,7 @@ void Instance::init(const int2 &output_res,
motion_blur.init();
main_view.init();
irradiance_cache.init();
reflection_probes.init();
}
void Instance::init_light_bake(Depsgraph *depsgraph, draw::Manager *manager)
@ -99,6 +100,7 @@ void Instance::init_light_bake(Depsgraph *depsgraph, draw::Manager *manager)
shadows.init();
main_view.init();
irradiance_cache.init();
reflection_probes.init();
}
void Instance::set_time(float time)
@ -146,6 +148,7 @@ void Instance::begin_sync()
hiz_buffer.sync();
main_view.sync();
world.sync();
reflection_probes.sync();
film.sync();
render_buffers.sync();
irradiance_cache.sync();
@ -514,6 +517,8 @@ void Instance::light_bake_irradiance(
render_sync();
manager->end_sync();
pipelines.world_probe.render();
irradiance_cache.bake.surfels_create(probe);
irradiance_cache.bake.surfels_lights_eval();
});

View File

@ -27,6 +27,7 @@
#include "eevee_material.hh"
#include "eevee_motion_blur.hh"
#include "eevee_pipeline.hh"
#include "eevee_reflection_probes.hh"
#include "eevee_renderbuffers.hh"
#include "eevee_sampling.hh"
#include "eevee_shader.hh"
@ -54,6 +55,7 @@ class Instance {
PipelineModule pipelines;
ShadowModule shadows;
LightModule lights;
ReflectionProbeModule reflection_probes;
VelocityModule velocity;
MotionBlurModule motion_blur;
DepthOfField depth_of_field;
@ -105,6 +107,7 @@ class Instance {
pipelines(*this),
shadows(*this),
lights(*this),
reflection_probes(*this),
velocity(*this),
motion_blur(*this),
depth_of_field(*this),

View File

@ -6,6 +6,10 @@
* \ingroup eevee
*/
/** \file
* \ingroup eevee
*/
#pragma once
#include "DNA_lightprobe_types.h"

View File

@ -56,6 +56,107 @@ void WorldPipeline::render(View &view)
/** \} */
/* -------------------------------------------------------------------- */
/** \name World Probe Pipeline
* \{ */
void WorldProbePipeline::sync()
{
for (int face : IndexRange(6)) {
CubemapSide &side = sides_[face];
/* View */
float4x4 view_m4 = cubeface_mat(face);
float4x4 win_m4;
cubeface_winmat_get(win_m4, 1.0f, 10.0f);
side.view.sync(view_m4, win_m4);
side.cubemap_face_ps.init();
side.cubemap_face_ps.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_ALWAYS);
}
const int2 extent(1);
constexpr eGPUTextureUsage usage = GPU_TEXTURE_USAGE_MEMORYLESS | GPU_TEXTURE_USAGE_SHADER_WRITE;
dummy_cryptomatte_tx_.ensure_2d(GPU_RGBA32F, extent, usage);
dummy_renderpass_tx_.ensure_2d(GPU_RGBA16F, extent, usage);
dummy_aov_color_tx_.ensure_2d_array(GPU_RGBA16F, extent, 1, usage);
dummy_aov_value_tx_.ensure_2d_array(GPU_R16F, extent, 1, usage);
has_draw_commands_ = false;
}
void WorldProbePipeline::sync(GPUMaterial *gpumat)
{
for (int face : IndexRange(6)) {
sync(gpumat, face);
}
has_draw_commands_ = true;
}
void WorldProbePipeline::sync(GPUMaterial *gpumat, int face)
{
Manager &manager = *inst_.manager;
CubemapSide &side = sides_[face];
/* Framebuffer. */
Texture &cubemap = inst_.reflection_probes.cubemaps_tx_;
side.cubemap_face_fb.ensure(GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE_CUBEFACE(cubemap, face));
ResourceHandle handle = manager.resource_handle(float4x4::identity());
PassSimple &pass = side.cubemap_face_ps;
pass.framebuffer_set(&side.cubemap_face_fb);
pass.material_set(manager, gpumat);
pass.push_constant("world_opacity_fade", 1.0f);
pass.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
pass.bind_ubo(CAMERA_BUF_SLOT, inst_.camera.ubo_get());
pass.bind_ubo(RBUFS_BUF_SLOT, &inst_.render_buffers.data);
pass.bind_image("rp_normal_img", dummy_renderpass_tx_);
pass.bind_image("rp_light_img", dummy_renderpass_tx_);
pass.bind_image("rp_diffuse_color_img", dummy_renderpass_tx_);
pass.bind_image("rp_specular_color_img", dummy_renderpass_tx_);
pass.bind_image("rp_emission_img", dummy_renderpass_tx_);
pass.bind_image("rp_cryptomatte_img", dummy_cryptomatte_tx_);
pass.bind_image("rp_color_img", dummy_aov_color_tx_);
pass.bind_image("rp_value_img", dummy_aov_value_tx_);
/* Required by validation layers. */
inst_.cryptomatte.bind_resources(&pass);
pass.bind_image("aov_color_img", dummy_aov_color_tx_);
pass.bind_image("aov_value_img", dummy_aov_value_tx_);
pass.bind_ssbo("aov_buf", &inst_.film.aovs_info);
pass.draw(DRW_cache_fullscreen_quad_get(), handle);
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
}
void WorldProbePipeline::render()
{
if (!has_draw_commands_) {
return;
}
GPUFrameBuffer *previous_framebuffer = GPU_framebuffer_active_get();
GPU_debug_group_begin("World.Probe");
for (int face : IndexRange(6)) {
sides_[face].render(inst_);
}
GPU_debug_group_end();
GPU_texture_update_mipmap_chain(inst_.reflection_probes.cubemaps_tx_);
if (previous_framebuffer) {
GPU_framebuffer_bind(previous_framebuffer);
}
}
void WorldProbePipeline::CubemapSide::render(Instance &instance)
{
instance.manager->submit(cubemap_face_ps, view);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shadow Pipeline
*
@ -372,6 +473,8 @@ void DeferredLayer::end_sync()
inst_.sampling.bind_resources(&eval_light_ps_);
inst_.hiz_buffer.bind_resources(&eval_light_ps_);
inst_.irradiance_cache.bind_resources(&eval_light_ps_);
inst_.reflection_probes.bind_resources(&eval_light_ps_);
inst_.irradiance_cache.bind_resources(&eval_light_ps_);
eval_light_ps_.barrier(GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_IMAGE_ACCESS);
eval_light_ps_.draw_procedural(GPU_PRIM_TRIS, 1, 3);

View File

@ -43,6 +43,56 @@ class WorldPipeline {
/** \} */
/* -------------------------------------------------------------------- */
/** \name World Probe Pipeline
*
* Render reflection probe of the world background.
* \{ */
class WorldProbePipeline {
private:
Instance &inst_;
Texture dummy_renderpass_tx_;
Texture dummy_cryptomatte_tx_;
Texture dummy_aov_color_tx_;
Texture dummy_aov_value_tx_;
struct CubemapSide {
PassSimple cubemap_face_ps;
View view;
Framebuffer cubemap_face_fb;
void render(Instance &instance);
};
/**
* Keep track if the world probe needs to be updated. This should only be the case when the
* world is updated. This flag is used to skip updating mipmaps when the world isn't changed.
*/
bool has_draw_commands_ = false;
CubemapSide sides_[6] = {
{{"PosX"}, {"PosX"}},
{{"NegX"}, {"NegX"}},
{{"PosY"}, {"PosY"}},
{{"NegY"}, {"NegY"}},
{{"PosZ"}, {"PosZ"}},
{{"NegZ"}, {"NegZ"}},
};
public:
WorldProbePipeline(Instance &inst) : inst_(inst){};
void sync();
void sync(GPUMaterial *gpumat);
void render();
private:
void sync(GPUMaterial *gpumat, int face);
}; // namespace blender::eevee
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shadow Pass
*
@ -288,6 +338,7 @@ class UtilityTexture : public Texture {
class PipelineModule {
public:
WorldPipeline world;
WorldProbePipeline world_probe;
DeferredPipeline deferred;
ForwardPipeline forward;
ShadowPipeline shadow;
@ -297,13 +348,19 @@ class PipelineModule {
public:
PipelineModule(Instance &inst)
: world(inst), deferred(inst), forward(inst), shadow(inst), capture(inst){};
: world(inst),
world_probe(inst),
deferred(inst),
forward(inst),
shadow(inst),
capture(inst){};
void begin_sync()
{
deferred.begin_sync();
forward.sync();
shadow.sync();
world_probe.sync();
capture.sync();
}

View File

@ -0,0 +1,75 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. */
#include "eevee_reflection_probes.hh"
#include "eevee_instance.hh"
namespace blender::eevee {
void ReflectionProbeModule::init()
{
if (cubemaps_.is_empty()) {
cubemaps_.reserve(MAX_PROBES);
/* Initialize the world cubemap. */
ReflectionProbe world_cubemap;
world_cubemap.type = ReflectionProbe::Type::World;
world_cubemap.is_dirty = true;
cubemaps_.append(world_cubemap);
cubemaps_tx_.ensure_cube_array(GPU_RGBA16F,
MAX_RESOLUTION,
MAX_PROBES,
GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_ATTACHMENT,
NULL,
MIPMAP_LEVELS);
GPU_texture_mipmap_mode(cubemaps_tx_, true, true);
dummy_tx_.ensure_cube_array(
GPU_RGBA16F, 1, 1, GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_ATTACHMENT, NULL, 1);
dummy_tx_.clear(float4(0.0f));
GPU_texture_mipmap_mode(dummy_tx_, false, false);
}
}
void ReflectionProbeModule::sync()
{
for (int index : IndexRange(MAX_PROBES)) {
ReflectionProbe &cubemap = cubemaps_[index];
if (!cubemap.needs_update()) {
continue;
}
sync(cubemap);
cubemap.is_dirty = false;
}
}
void ReflectionProbeModule::sync(const ReflectionProbe &cubemap)
{
if (cubemap.type == ReflectionProbe::Type::World) {
GPUMaterial *world_material = instance_.world.get_world_material();
instance_.pipelines.world_probe.sync(world_material);
}
else {
BLI_assert_unreachable();
}
}
void ReflectionProbeModule::set_world_dirty()
{
cubemaps_[WORLD_SLOT].is_dirty = true;
}
/* -------------------------------------------------------------------- */
/** \name World
*
* \{ */
bool ReflectionProbe::needs_update() const
{
return type != Type::Unused && is_dirty;
}
/** \} */
} // namespace blender::eevee

View File

@ -0,0 +1,96 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. */
/** \file
* \ingroup eevee
*
* Cubemaps
*
* Cubemaps record light from different locations in the scene. These cubemaps are used to add
* environment and indirect lighting from light probes.
*
* - Although we have Global illumination light probes can still be used for situations that
* GI doesn't work. For example semi-transparent surfaces.
* - The first cubemap is always reserved for the world shading.
*
*/
#pragma once
#include "eevee_shader_shared.hh"
#include "BKE_cryptomatte.hh"
extern "C" {
struct Material;
}
namespace blender::eevee {
class Instance;
class WorldProbePipeline;
/* -------------------------------------------------------------------- */
/** \name Reflection Probes
* \{ */
class ReflectionProbe {
public:
enum Type { Unused, World };
Type type;
bool is_dirty = false;
bool needs_update() const;
};
class ReflectionProbeModule {
private:
/** The max number of probes to track. */
static constexpr int MAX_PROBES = 1;
/**
* The maximum resolution of a cubemap side.
*
* Must be a power of two; intension to be used as a cubemap atlas.
*/
static constexpr int MAX_RESOLUTION = 2048;
static constexpr int MIPMAP_LEVELS = 12;
/**
* Index of the probe that is used for world background.
*
* NOTE: First probe always contains the world probe.
*/
static constexpr int WORLD_SLOT = 0;
Instance &instance_;
Vector<ReflectionProbe> cubemaps_;
Texture cubemaps_tx_ = {"Probes"};
Texture dummy_tx_ = {"DummyProbes"};
public:
ReflectionProbeModule(Instance &instance) : instance_(instance) {}
void init();
void set_world_dirty();
void sync();
template<typename T> void bind_resources(draw::detail::PassBase<T> *pass)
{
pass->bind_texture(REFLECTION_PROBE_TEX_SLOT, cubemaps_tx_);
}
template<typename T> void bind_dummy_resources(draw::detail::PassBase<T> *pass)
{
pass->bind_texture(REFLECTION_PROBE_TEX_SLOT, dummy_tx_);
}
private:
void sync(const ReflectionProbe &cubemap);
friend class WorldProbePipeline;
};
} // namespace blender::eevee

View File

@ -118,6 +118,7 @@ void ShadingView::render()
GPU_framebuffer_bind(combined_fb_);
GPU_framebuffer_clear_color_depth(combined_fb_, clear_color, 1.0f);
inst_.pipelines.world_probe.render();
inst_.pipelines.world.render(render_view_new_);
/* TODO(fclem): Move it after the first prepass (and hiz update) once pipeline is stabilized. */
@ -125,8 +126,6 @@ void ShadingView::render()
inst_.pipelines.deferred.render(render_view_new_, prepass_fb_, combined_fb_, extent_);
// inst_.lightprobes.draw_cache_display();
// inst_.lookdev.render_overlay(view_fb_);
inst_.pipelines.forward.render(render_view_new_, prepass_fb_, combined_fb_, rbufs.combined_tx);

View File

@ -90,7 +90,7 @@ void World::sync()
WorldHandle &wo_handle = inst_.sync.sync_world(bl_world);
if (wo_handle.recalc != 0) {
// inst_.lightprobes.set_world_dirty();
inst_.reflection_probes.set_world_dirty();
}
wo_handle.reset_recalc_flag();
@ -111,6 +111,21 @@ void World::sync()
inst_.pipelines.world.sync(gpumat);
}
GPUMaterial *World::get_world_material()
{
::World *bl_world = inst_.scene->world;
if (bl_world == nullptr) {
bl_world = default_world_get();
}
bNodeTree *ntree = (bl_world->nodetree && bl_world->use_nodes) ?
bl_world->nodetree :
default_tree.nodetree_get(bl_world);
GPUMaterial *gpumat = inst_.shaders.world_shader_get(bl_world, ntree);
return gpumat;
}
/** \} */
} // namespace blender::eevee

View File

@ -62,6 +62,13 @@ class World {
~World();
void sync();
/**
* Get the world material.
*
* NOTE: this function should only be called after World::sync has been executed.
*/
GPUMaterial *get_world_material();
};
/** \} */

View File

@ -10,6 +10,8 @@
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_light_eval_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_lightprobe_eval_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_reflection_probe_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_lightprobe_eval_lib.glsl)
void main()
{
@ -56,6 +58,8 @@ void main()
lightprobe_eval(diffuse_data, reflection_data, P, Ng, V, diffuse_light, reflection_light);
light_world_eval(reflection_data, P, V, reflection_light);
light_eval(diffuse_data,
reflection_data,
P,

View File

@ -13,6 +13,7 @@
#pragma BLENDER_REQUIRE(eevee_surfel_list_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_lightprobe_lib.glsl)
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
#pragma BLENDER_REQUIRE(cubemap_lib.glsl)
void irradiance_capture(vec3 L, vec3 irradiance, inout SphericalHarmonicL1 sh)
{
@ -40,6 +41,11 @@ void irradiance_capture(Surfel surfel, vec3 P, inout SphericalHarmonicL1 sh)
irradiance_capture(L, irradiance, sh);
}
vec3 irradiance_sky_sample(vec3 R)
{
return textureLod_cubemapArray(reflectionProbes, vec4(R, 0.0), 0.0).rgb;
}
void main()
{
ivec3 grid_coord = ivec3(gl_GlobalInvocationID);
@ -88,8 +94,8 @@ void main()
irradiance_capture(surfel, P, sh);
}
else {
/* TODO(fclem): Sky radiance. */
irradiance_capture(sky_L, vec3(0.0), sh);
vec3 world_radiance = irradiance_sky_sample(sky_L);
irradiance_capture(sky_L, world_radiance, sh);
}
if (surfel_prev > -1) {
@ -97,8 +103,8 @@ void main()
irradiance_capture(surfel, P, sh);
}
else {
/* TODO(fclem): Sky radiance. */
irradiance_capture(-sky_L, vec3(0.0), sh);
vec3 world_radiance = irradiance_sky_sample(-sky_L);
irradiance_capture(-sky_L, world_radiance, sh);
}
/* Normalize for storage. We accumulated 2 samples. */

View File

@ -0,0 +1,12 @@
void light_world_eval(ClosureReflection reflection, vec3 P, vec3 V, inout vec3 out_specular)
{
float linear_roughness = fast_sqrt(reflection.roughness);
/* TODO: This should be based by actual resolution. Currently the resolution is fixed but
* eventually this should based on a user setting. */
float lod_cube_max = 12.0;
float lod = linear_roughness * lod_cube_max;
vec3 R = -reflect(V, reflection.N);
vec3 world_light = textureLod_cubemapArray(reflectionProbes, vec4(R, 0.0), lod).rgb;
out_specular += world_light;
}

View File

@ -12,6 +12,7 @@
#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl)
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
#pragma BLENDER_REQUIRE(cubemap_lib.glsl)
void radiance_transfer(inout Surfel surfel, vec3 in_radiance, vec3 L)
{
@ -69,6 +70,11 @@ void radiance_transfer_world(inout Surfel receiver, vec3 sky_L)
radiance_transfer(receiver, radiance, -sky_L);
}
vec3 radiance_sky_sample(vec3 R)
{
return textureLod_cubemapArray(reflectionProbes, vec4(R, 0.0), 0.0).rgb;
}
void main()
{
int surfel_index = int(gl_GlobalInvocationID.x);
@ -85,7 +91,8 @@ void main()
radiance_transfer_surfel(surfel, surfel_next);
}
else {
radiance_transfer_world(surfel, sky_L);
vec3 world_radiance = radiance_sky_sample(sky_L);
radiance_transfer(surfel, world_radiance, sky_L);
}
if (surfel.prev > -1) {
@ -93,7 +100,8 @@ void main()
radiance_transfer_surfel(surfel, surfel_prev);
}
else {
radiance_transfer_world(surfel, -sky_L);
vec3 world_radiance = radiance_sky_sample(-sky_L);
radiance_transfer(surfel, world_radiance, -sky_L);
}
surfel_buf[surfel_index] = surfel;

View File

@ -38,6 +38,7 @@ GPU_SHADER_CREATE_INFO(eevee_deferred_light)
"eevee_utility_texture",
"eevee_light_data",
"eevee_lightprobe_data",
"eevee_reflection_probe_data",
"eevee_shadow_data",
"eevee_deferred_base",
"eevee_transmittance_data",

View File

@ -83,7 +83,10 @@ GPU_SHADER_CREATE_INFO(eevee_surfel_list_sort)
GPU_SHADER_CREATE_INFO(eevee_surfel_ray)
.local_group_size(SURFEL_GROUP_SIZE)
.additional_info("eevee_shared", "eevee_surfel_common", "draw_view")
.additional_info("eevee_shared",
"eevee_surfel_common",
"eevee_reflection_probe_data",
"draw_view")
.push_constant(Type::INT, "radiance_src")
.push_constant(Type::INT, "radiance_dst")
.compute_source("eevee_surfel_ray_comp.glsl")
@ -103,7 +106,10 @@ GPU_SHADER_CREATE_INFO(eevee_lightprobe_irradiance_ray)
.local_group_size(IRRADIANCE_GRID_GROUP_SIZE,
IRRADIANCE_GRID_GROUP_SIZE,
IRRADIANCE_GRID_GROUP_SIZE)
.additional_info("eevee_shared", "eevee_surfel_common", "draw_view")
.additional_info("eevee_shared",
"eevee_surfel_common",
"eevee_reflection_probe_data",
"draw_view")
.push_constant(Type::INT, "radiance_src")
.storage_buf(0, Qualifier::READ, "int", "list_start_buf[]")
.storage_buf(6, Qualifier::READ, "SurfelListInfoData", "list_info_buf")

View File

@ -0,0 +1,11 @@
#include "eevee_defines.hh"
#include "gpu_shader_create_info.hh"
/* -------------------------------------------------------------------- */
/** \name Shared
* \{ */
GPU_SHADER_CREATE_INFO(eevee_reflection_probe_data)
.sampler(REFLECTION_PROBE_TEX_SLOT, ImageType::FLOAT_CUBE_ARRAY, "reflectionProbes");
/** \} */

View File

@ -691,7 +691,7 @@ class Texture : NonCopyable {
float *data = nullptr,
int mip_len = 1)
{
return ensure_impl(extent, extent, layers, mip_len, format, usage, data, false, true);
return ensure_impl(extent, extent, layers, mip_len, format, usage, data, true, true);
}
/**

View File

@ -651,6 +651,7 @@ set(SRC_SHADER_CREATE_INFOS
../draw/engines/eevee_next/shaders/infos/eevee_light_culling_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_material_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_motion_blur_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_reflection_probe_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_shadow_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_subsurface_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_velocity_info.hh

View File

@ -895,6 +895,11 @@ static char *glsl_patch_compute_get()
STR_CONCAT(patch, slen, "#version 430\n");
STR_CONCAT(patch, slen, "#extension GL_ARB_compute_shader :enable\n");
if (GLContext::texture_cube_map_array_support) {
STR_CONCAT(patch, slen, "#extension GL_ARB_texture_cube_map_array : enable\n");
STR_CONCAT(patch, slen, "#define GPU_ARB_texture_cube_map_array\n");
}
/* Array compat. */
STR_CONCAT(patch, slen, "#define gpu_Array(_type) _type[]\n");