EEVEE-Next: Shadow: Add LOD system to directional clipmap shadows #120031

Merged
Clément Foucault merged 16 commits from fclem/blender:eevee-next-shadow-directional-lod into main 2024-03-29 16:23:06 +01:00
15 changed files with 632 additions and 34 deletions

View File

@ -586,6 +586,7 @@ set(GLSL_SRC
engines/eevee_next/shaders/eevee_shadow_tag_usage_vert.glsl
engines/eevee_next/shaders/eevee_shadow_tag_usage_volume_comp.glsl
engines/eevee_next/shaders/eevee_shadow_test.glsl
engines/eevee_next/shaders/eevee_shadow_tilemap_amend_comp.glsl
engines/eevee_next/shaders/eevee_shadow_tilemap_bounds_comp.glsl
engines/eevee_next/shaders/eevee_shadow_tilemap_finalize_comp.glsl
engines/eevee_next/shaders/eevee_shadow_tilemap_init_comp.glsl

View File

@ -87,6 +87,8 @@
#define SHADOW_TILEDATA_PER_TILEMAP \
(SHADOW_TILEMAP_LOD0_LEN + SHADOW_TILEMAP_LOD1_LEN + SHADOW_TILEMAP_LOD2_LEN + \
SHADOW_TILEMAP_LOD3_LEN + SHADOW_TILEMAP_LOD4_LEN + SHADOW_TILEMAP_LOD5_LEN)
/* Maximum number of relative LOD distance we can store. */
#define SHADOW_TILEMAP_MAX_CLIPMAP_LOD 8
#if 0
/* Useful for debugging the tile-copy version of the shadow rendering without making debugging
* tools unresponsive. */

View File

@ -241,6 +241,8 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_
return "eevee_shadow_page_free";
case SHADOW_PAGE_MASK:
return "eevee_shadow_page_mask";
case SHADOW_TILEMAP_AMEND:
return "eevee_shadow_tilemap_amend";
case SHADOW_TILEMAP_BOUNDS:
return "eevee_shadow_tilemap_bounds";
case SHADOW_TILEMAP_FINALIZE:

View File

@ -120,6 +120,7 @@ enum eShaderType {
SHADOW_PAGE_MASK,
SHADOW_PAGE_TILE_CLEAR,
SHADOW_PAGE_TILE_STORE,
SHADOW_TILEMAP_AMEND,
SHADOW_TILEMAP_BOUNDS,
SHADOW_TILEMAP_FINALIZE,
SHADOW_TILEMAP_INIT,

View File

@ -1231,8 +1231,6 @@ struct ShadowTileData {
uint3 page;
/** Page index inside pages_cached_buf. Only valid if `is_cached` is true. */
uint cache_index;
/** LOD pointed to LOD 0 tile page. (cube-map only). */
uint lod;
/** If the tile is needed for rendering. */
bool is_used;
/** True if an update is needed. This persists even if the tile gets unused. */
@ -1279,8 +1277,7 @@ static inline ShadowTileData shadow_tile_unpack(ShadowTileDataPacked data)
ShadowTileData tile;
tile.page = shadow_page_unpack(data);
/* -- 12 bits -- */
BLI_STATIC_ASSERT(SHADOW_TILEMAP_LOD < 8, "Update page packing")
tile.lod = (data >> 12u) & 7u;
/* Unused bits. */
/* -- 15 bits -- */
BLI_STATIC_ASSERT(SHADOW_MAX_PAGE <= 4096, "Update page packing")
tile.cache_index = (data >> 15u) & 4095u;
@ -1299,7 +1296,6 @@ static inline ShadowTileDataPacked shadow_tile_pack(ShadowTileData tile)
/* NOTE: Page might be set to invalid values for tracking invalid usages.
* So we have to mask the result. */
data = shadow_page_pack(tile.page) & uint(SHADOW_MAX_PAGE - 1);
data |= (tile.lod & 7u) << 12u;
data |= (tile.cache_index & 4095u) << 15u;
data |= (tile.is_used ? uint(SHADOW_IS_USED) : 0);
data |= (tile.is_allocated ? uint(SHADOW_IS_ALLOCATED) : 0);
@ -1309,6 +1305,89 @@ static inline ShadowTileDataPacked shadow_tile_pack(ShadowTileData tile)
return data;
}
/**
* Decoded tile data structure.
* Similar to ShadowTileData, this one is only used for rendering and packed into `tilemap_tx`.
* This allow to reuse some bits for other purpose.
*/
struct ShadowSamplingTile {
/** Page inside the virtual shadow map atlas. */
uint3 page;
/** LOD pointed to LOD 0 tile page. */
uint lod;
/** Offset to the texel position to align with the LOD page start. (directional only). */
uint2 lod_offset;
/** If the tile is needed for rendering. */
bool is_valid;
};
/** \note Stored packed as a uint. */
#define ShadowSamplingTilePacked uint
/* NOTE: Trust the input to be in valid range [0, (1 << SHADOW_TILEMAP_MAX_CLIPMAP_LOD) - 1].
* Maximum LOD level index we can store is SHADOW_TILEMAP_MAX_CLIPMAP_LOD,
* so we need SHADOW_TILEMAP_MAX_CLIPMAP_LOD bits to store the offset in each dimension.
* Result fits into SHADOW_TILEMAP_MAX_CLIPMAP_LOD * 2 bits. */
static inline uint shadow_lod_offset_pack(uint2 ofs)
{
BLI_STATIC_ASSERT(SHADOW_TILEMAP_MAX_CLIPMAP_LOD <= 8, "Update page packing")
return ofs.x | (ofs.y << SHADOW_TILEMAP_MAX_CLIPMAP_LOD);
}
static inline uint2 shadow_lod_offset_unpack(uint data)
{
return (uint2(data) >> uint2(0, SHADOW_TILEMAP_MAX_CLIPMAP_LOD)) &
uint2((1 << SHADOW_TILEMAP_MAX_CLIPMAP_LOD) - 1);
}
static inline ShadowSamplingTile shadow_sampling_tile_unpack(ShadowSamplingTilePacked data)
{
ShadowSamplingTile tile;
tile.page = shadow_page_unpack(data);
/* -- 12 bits -- */
/* Max value is actually SHADOW_TILEMAP_MAX_CLIPMAP_LOD but we mask the bits. */
tile.lod = (data >> 12u) & 15u;
/* -- 16 bits -- */
tile.lod_offset = shadow_lod_offset_unpack(data >> 16u);
/* -- 32 bits -- */
tile.is_valid = data != 0u;
#ifndef GPU_SHADER
/* Make tests pass on CPU but it is not required for proper rendering. */
if (tile.lod == 0) {
tile.lod_offset.x = 0;
}
#endif
return tile;
}
static inline ShadowSamplingTilePacked shadow_sampling_tile_pack(ShadowSamplingTile tile)
{
if (!tile.is_valid) {
return 0u;
}
/* Tag a valid tile of LOD0 valid by setting their offset to 1.
* This doesn't change the sampling and allows to use of all bits for data.
* This makes sure no valid packed tile is 0u. */
if (tile.lod == 0) {
tile.lod_offset.x = 1;
}
uint data = shadow_page_pack(tile.page);
/* Max value is actually SHADOW_TILEMAP_MAX_CLIPMAP_LOD but we mask the bits. */
data |= (tile.lod & 15u) << 12u;
data |= shadow_lod_offset_pack(tile.lod_offset) << 16u;
return data;
}
static inline ShadowSamplingTile shadow_sampling_tile_create(ShadowTileData tile_data, uint lod)
{
ShadowSamplingTile tile;
tile.page = tile_data.page;
tile.lod = lod;
tile.lod_offset = uint2(0, 0); /* Computed during tilemap amend phase. */
/* At this point, it should be the case that all given tiles that have been tagged as used are
* ready for sampling. Otherwise tile_data should be SHADOW_NO_DATA. */
tile.is_valid = tile_data.is_used;
return tile;
}
struct ShadowSceneData {
/* Number of shadow rays to shoot for each light. */
int ray_count;

View File

@ -1199,6 +1199,15 @@ void ShadowModule::end_sync()
sub.barrier(GPU_BARRIER_SHADER_STORAGE | GPU_BARRIER_UNIFORM | GPU_BARRIER_TEXTURE_FETCH |
GPU_BARRIER_SHADER_IMAGE_ACCESS);
}
{
/* Amend tilemap_tx content to support clipmap LODs. */
PassSimple::Sub &sub = pass.sub("Amend");
sub.shader_set(inst_.shaders.static_shader_get(SHADOW_TILEMAP_AMEND));
sub.bind_image("tilemaps_img", tilemap_pool.tilemap_tx);
sub.bind_resources(inst_.lights);
sub.dispatch(int3(1));
sub.barrier(GPU_BARRIER_TEXTURE_FETCH);
}
/* NOTE: We do not need to run the clear pass when using the TBDR update variant, as tiles
* will be fully cleared as part of the shadow raster step. */

View File

@ -33,7 +33,6 @@ void debug_tile_print(ShadowTileData tile, ivec4 tile_coord)
{
#ifdef DRW_DEBUG_PRINT
drw_print("Tile (", tile_coord.x, ",", tile_coord.y, ") in Tilemap ", tile_coord.z, " : ");
drw_print(tile.lod);
drw_print(tile.page);
drw_print(tile.cache_index);
#endif
@ -41,10 +40,6 @@ void debug_tile_print(ShadowTileData tile, ivec4 tile_coord)
vec3 debug_tile_state_color(ShadowTileData tile)
{
if (tile.lod > 0) {
/* Uses data from another LOD. */
return neon_gradient(float(tile.lod) / float(SHADOW_TILEMAP_LOD));
}
if (tile.do_update && tile.is_used) {
/* Updated. */
return vec3(0.5, 1, 0);
@ -63,6 +58,17 @@ vec3 debug_tile_state_color(ShadowTileData tile)
return col;
}
vec3 debug_tile_state_color(eLightType type, ShadowSamplingTile tile)
{
if (!tile.is_valid) {
return vec3(1, 0, 0);
}
/* Uses data from another LOD. */
return neon_gradient(float(tile.lod) / float((type == LIGHT_SUN) ?
SHADOW_TILEMAP_MAX_CLIPMAP_LOD :
SHADOW_TILEMAP_LOD));
}
ShadowSampleParams debug_shadow_sample_get(vec3 P, LightData light)
{
if (is_sun_light(light.type)) {
@ -73,7 +79,7 @@ ShadowSampleParams debug_shadow_sample_get(vec3 P, LightData light)
}
}
ShadowTileData debug_tile_get(vec3 P, LightData light)
ShadowSamplingTile debug_tile_get(vec3 P, LightData light)
{
return shadow_tile_data_get(shadow_tilemaps_tx, debug_shadow_sample_get(P, light));
}
@ -106,6 +112,26 @@ bool debug_tilemaps(vec3 P, LightData light)
int tilemap = px.x / SHADOW_TILEMAP_RES;
int tilemap_index = light.tilemap_index + tilemap;
if ((px.y < SHADOW_TILEMAP_RES) && (tilemap_index <= light_tilemap_max_get(light))) {
#if 1
/* Debug values in the tilemap_tx. */
ivec2 tilemap_texel = shadow_tile_coord_in_atlas(px, tilemap_index);
ShadowSamplingTile tile = shadow_sampling_tile_unpack(
texelFetch(shadow_tilemaps_tx, tilemap_texel, 0).x);
/* Leave 1 px border between tile-maps. */
if (!any(equal(ivec2(gl_FragCoord.xy) % (SHADOW_TILEMAP_RES * debug_tile_size_px), ivec2(0))))
{
gl_FragDepth = 0.0;
out_color_add = vec4(debug_tile_state_color(light.type, tile), 0.0);
out_color_mul = vec4(0.0);
# ifdef DRW_DEBUG_PRINT
if (all(equal(ivec2(gl_FragCoord.xy), ivec2(0)))) {
drw_print(light.object_mat);
}
# endif
return true;
}
#else
/* Debug actual values in the tile-map buffer. */
ShadowTileMapData tilemap = tilemaps_buf[tilemap_index];
int tile_index = shadow_tile_offset(
@ -118,21 +144,22 @@ bool debug_tilemaps(vec3 P, LightData light)
out_color_add = vec4(debug_tile_state_color(tile), 0.0);
out_color_mul = vec4(0.0);
#ifdef DRW_DEBUG_PRINT
# ifdef DRW_DEBUG_PRINT
if (all(equal(ivec2(gl_FragCoord.xy), ivec2(0)))) {
drw_print(light.object_mat);
}
#endif
# endif
return true;
}
#endif
}
return false;
}
void debug_tile_state(vec3 P, LightData light)
{
ShadowTileData tile = debug_tile_get(P, light);
out_color_add = vec4(debug_tile_state_color(tile), 0) * 0.5;
ShadowSamplingTile tile = debug_tile_get(P, light);
out_color_add = vec4(debug_tile_state_color(light.type, tile), 0) * 0.5;
out_color_mul = vec4(0.5);
}
@ -146,7 +173,7 @@ void debug_atlas_values(vec3 P, LightData light)
void debug_random_tile_color(vec3 P, LightData light)
{
ShadowTileData tile = debug_tile_get(P, light);
ShadowSamplingTile tile = debug_tile_get(P, light);
out_color_add = vec4(debug_random_color(ivec2(tile.page.xy)), 0) * 0.5;
out_color_mul = vec4(0.5);
}

View File

@ -21,7 +21,7 @@ struct ShadowSampleParams {
float z_range;
};
ShadowTileData shadow_tile_data_get(usampler2D tilemaps_tx, ShadowSampleParams params)
ShadowSamplingTile shadow_tile_data_get(usampler2D tilemaps_tx, ShadowSampleParams params)
{
/* Prevent out of bound access. Assumes the input is already non negative. */
vec2 tilemap_uv = min(params.uv.xy, vec2(0.99999));
@ -46,9 +46,9 @@ float shadow_read_depth(SHADOW_ATLAS_TYPE atlas_tx,
const int page_shift = SHADOW_PAGE_LOD;
ivec2 tile_coord = texel_coord >> page_shift;
ShadowTileData tile = shadow_tile_load(tilemaps_tx, tile_coord, params.tilemap_index);
ShadowSamplingTile tile = shadow_tile_load(tilemaps_tx, tile_coord, params.tilemap_index);
if (!tile.is_allocated) {
if (!tile.is_valid) {
return -1.0;
}

View File

@ -26,7 +26,6 @@ void main()
ShadowTileData tile = shadow_tile_unpack(tiles_buf[tile_index]);
if (tile.is_used && !tile.is_allocated) {
shadow_page_alloc(tile);
tile.lod = lod;
tiles_buf[tile_index] = shadow_tile_pack(tile);
}

View File

@ -0,0 +1,93 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Virtual shadow-mapping: Amend sampling tile atlas.
*
* In order to support sampling different LOD for clipmap shadow projections, we need to scan
* through the LOD tilemaps from lowest LOD to highest LOD, gathering the last valid tile along the
* way for the current destination tile. For each new level we gather the previous level tiles from
* local memory using the correct relative offset from the previous level as they might not be
* aligned.
*
* TODO(fclem): This shader **should** be dispatched for one thread-group per directional light.
* Currently this shader is dispatched with one thread-group for all directional light.
*/
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_light_iter_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl)
shared ShadowSamplingTilePacked tiles_local[SHADOW_TILEMAP_RES][SHADOW_TILEMAP_RES];
void main()
{
ivec2 tile_co = ivec2(gl_GlobalInvocationID.xy);
LIGHT_FOREACH_BEGIN_DIRECTIONAL (light_cull_buf, l_idx) {
LightData light = light_buf[l_idx];
/* This only works on clipmaps. Cascade have already the same LOD for every tilemaps. */
if (light.type != LIGHT_SUN) {
break;
}
ivec2 base_offset_neg = light_sun_data_get(light).clipmap_base_offset_neg;
ivec2 base_offset_pos = light_sun_data_get(light).clipmap_base_offset_pos;
/* LOD relative max with respect to clipmap_lod_min. */
int lod_max = light_sun_data_get(light).clipmap_lod_max -
light_sun_data_get(light).clipmap_lod_min;
/* Iterate in reverse. */
for (int lod = lod_max; lod >= 0; lod--) {
int tilemap_index = light.tilemap_index + lod;
ivec2 atlas_texel = shadow_tile_coord_in_atlas(tile_co, tilemap_index);
ShadowSamplingTilePacked tile_packed = imageLoad(tilemaps_img, atlas_texel).x;
ShadowSamplingTile tile = shadow_sampling_tile_unpack(tile_packed);
if (lod != lod_max && !tile.is_valid) {
/* Offset this LOD has with the previous one. In unit of tile of the current LOD. */
ivec2 offset_binary = ((base_offset_pos >> lod) & 1) - ((base_offset_neg >> lod) & 1);
ivec2 offset_centered = ivec2(SHADOW_TILEMAP_RES / 2) + offset_binary;
ivec2 tile_co_prev = (tile_co + offset_centered) >> 1;
/* Load tile from the previous LOD. */
ShadowSamplingTilePacked tile_prev_packed = tiles_local[tile_co_prev.y][tile_co_prev.x];
ShadowSamplingTile tile_prev = shadow_sampling_tile_unpack(tile_prev_packed);
/* We can only propagate LODs up to a certain level.
* Afterwards we run out of bits to store the offsets. */
if (tile_prev.is_valid && tile_prev.lod < SHADOW_TILEMAP_MAX_CLIPMAP_LOD - 1) {
/* Relative LOD. Used for reducing pixel rate at sampling time.
* Increase with each new invalid level. */
tile_prev.lod += 1;
/* The offset (in tile of current LOD) is equal to the offset from the bottom left corner
* of both LODs modulo the size of a tile of the source LOD (in tile of current LOD). */
/* Offset corner to center. */
tile_prev.lod_offset = uvec2(SHADOW_TILEMAP_RES / 2) << tile_prev.lod;
/* Align center of both LODs. */
tile_prev.lod_offset -= uvec2(SHADOW_TILEMAP_RES / 2);
/* Add the offset relative to the source LOD. */
tile_prev.lod_offset += uvec2(bitfieldExtract(base_offset_pos, lod, int(tile_prev.lod)) -
bitfieldExtract(base_offset_neg, lod, int(tile_prev.lod)));
/* Wrap to valid range. */
tile_prev.lod_offset &= ~(~0u << tile_prev.lod);
tile_prev_packed = shadow_sampling_tile_pack(tile_prev);
/* Replace the missing page with the one from the lower LOD. */
imageStore(tilemaps_img, atlas_texel, uvec4(tile_prev_packed));
/* Push this amended tile to the local tiles. */
tile_packed = tile_prev_packed;
tile.is_valid = true;
}
}
barrier();
tiles_local[tile_co.y][tile_co.x] = (tile.is_valid) ? tile_packed : SHADOW_NO_DATA;
barrier();
}
}
LIGHT_FOREACH_END
}

View File

@ -57,6 +57,7 @@ void main()
bool is_cubemap = (tilemap_data.projection_type == SHADOW_PROJECTION_CUBEFACE);
int lod_max = is_cubemap ? SHADOW_TILEMAP_LOD : 0;
int valid_tile_index = -1;
uint valid_lod = 0u;
/* With all threads (LOD0 size dispatch) load each lod tile from the highest lod
* to the lowest, keeping track of the lowest one allocated which will be use for shadowing.
* This guarantee a O(1) lookup time.
@ -176,12 +177,17 @@ void main()
if (tile.is_used && tile.is_allocated && (!tile.do_update || lod_is_rendered)) {
/* Save highest lod for this thread. */
valid_tile_index = tile_index;
valid_lod = uint(lod);
}
}
/* Store the highest LOD valid page for rendering. */
uint tile_packed = (valid_tile_index != -1) ? tiles_buf[valid_tile_index] : SHADOW_NO_DATA;
imageStore(tilemaps_img, atlas_texel, uvec4(tile_packed));
ShadowTileDataPacked tile_packed = (valid_tile_index != -1) ? tiles_buf[valid_tile_index] :
SHADOW_NO_DATA;
ShadowTileData tile_data = shadow_tile_unpack(tile_packed);
ShadowSamplingTile tile_sampling = shadow_sampling_tile_create(tile_data, valid_lod);
ShadowSamplingTilePacked tile_sampling_packed = shadow_sampling_tile_pack(tile_sampling);
imageStore(tilemaps_img, atlas_texel, uvec4(tile_sampling_packed));
if (all(equal(gl_GlobalInvocationID, uvec3(0)))) {
/* Clamp it as it can underflow if there is too much tile present on screen. */

View File

@ -91,14 +91,14 @@ int shadow_tile_offset(ivec2 tile, int tiles_index, int lod)
* \{ */
/** \note: Will clamp if out of bounds. */
ShadowTileData shadow_tile_load(usampler2D tilemaps_tx, ivec2 tile_co, int tilemap_index)
ShadowSamplingTile shadow_tile_load(usampler2D tilemaps_tx, ivec2 tile_co, int tilemap_index)
{
/* NOTE(@fclem): This clamp can hide some small imprecision at clip-map transition.
* Can be disabled to check if the clip-map is well centered. */
tile_co = clamp(tile_co, ivec2(0), ivec2(SHADOW_TILEMAP_RES - 1));
uint tile_data =
texelFetch(tilemaps_tx, shadow_tile_coord_in_atlas(tile_co, tilemap_index), 0).x;
return shadow_tile_unpack(tile_data);
ivec2 texel = shadow_tile_coord_in_atlas(tile_co, tilemap_index);
uint tile_data = texelFetch(tilemaps_tx, texel, 0).x;
return shadow_sampling_tile_unpack(tile_data);
}
/**

View File

@ -23,16 +23,19 @@ float shadow_read_depth_at_tilemap_uv(int tilemap_index, vec2 tilemap_uv)
ivec2 texel_coord = ivec2(tilemap_uv * float(SHADOW_MAP_MAX_RES));
/* Using bitwise ops is way faster than integer ops. */
const int page_shift = SHADOW_PAGE_LOD;
const int page_mask = ~(0xFFFFFFFF << SHADOW_PAGE_LOD);
ivec2 tile_coord = texel_coord >> page_shift;
ShadowTileData tile = shadow_tile_load(shadow_tilemaps_tx, tile_coord, tilemap_index);
ShadowSamplingTile tile = shadow_tile_load(shadow_tilemaps_tx, tile_coord, tilemap_index);
if (!tile.is_allocated) {
if (!tile.is_valid) {
return -1.0;
}
int page_mask = ~(0xFFFFFFFF << (SHADOW_PAGE_LOD + int(tile.lod)));
ivec2 texel_page = (texel_coord & page_mask) >> int(tile.lod);
/* Shift LOD0 pixels so that they get wrapped at the right position for the given LOD. */
/* TODO convert everything to uint to avoid signed int operations. */
texel_coord += ivec2(tile.lod_offset << SHADOW_PAGE_LOD);
/* Scale to LOD pixels (merge LOD0 pixels together) then mask to get pixel in page. */
ivec2 texel_page = (texel_coord >> int(tile.lod)) & page_mask;
ivec3 texel = ivec3((ivec2(tile.page.xy) << page_shift) | texel_page, tile.page.z);
return uintBitsToFloat(texelFetch(shadow_atlas_tx, texel, 0).r);
@ -439,8 +442,8 @@ vec3 shadow_pcf_offset(LightData light, const bool is_directional, vec3 P, vec3
else {
params = shadow_punctual_sample_params_get(light, P);
}
ShadowTileData tile = shadow_tile_data_get(shadow_tilemaps_tx, params);
if (!tile.is_allocated) {
ShadowSamplingTile tile = shadow_tile_data_get(shadow_tilemaps_tx, params);
if (!tile.is_valid) {
return vec3(0.0);
}

View File

@ -195,6 +195,13 @@ GPU_SHADER_CREATE_INFO(eevee_shadow_tilemap_finalize)
.additional_info("eevee_shared")
.compute_source("eevee_shadow_tilemap_finalize_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_shadow_tilemap_amend)
.do_static_compilation(true)
.local_group_size(SHADOW_TILEMAP_RES, SHADOW_TILEMAP_RES)
.image(0, GPU_R32UI, Qualifier::READ_WRITE, ImageType::UINT_2D, "tilemaps_img")
.additional_info("eevee_shared", "eevee_light_data", "draw_view")
.compute_source("eevee_shadow_tilemap_amend_comp.glsl");
/* AtomicMin clear implementation. */
GPU_SHADER_CREATE_INFO(eevee_shadow_page_clear)
.do_static_compilation(true)

View File

@ -10,7 +10,7 @@
#include "BKE_node.hh"
#include "BKE_object.hh"
#include "DEG_depsgraph.hh"
#include "BLI_vector.hh"
#include "RNA_define.hh"
@ -1164,6 +1164,375 @@ static void test_eevee_shadow_finalize()
}
DRAW_TEST(eevee_shadow_finalize)
static void test_eevee_shadow_tile_packing()
{
Vector<uint> test_values{0x00000000u, 0x00000001u, 0x0000000Fu, 0x000000FFu, 0xABCDEF01u,
0xAAAAAAAAu, 0xBBBBBBBBu, 0xCCCCCCCCu, 0xDDDDDDDDu, 0xEEEEEEEEu,
0xFFFFFFFFu, 0xDEADBEEFu, 0x8BADF00Du, 0xABADCAFEu, 0x0D15EA5Eu,
0xFEE1DEADu, 0xDEADC0DEu, 0xC00010FFu, 0xBBADBEEFu, 0xBAAAAAADu};
for (auto value : test_values) {
EXPECT_EQ(shadow_page_unpack(value),
shadow_page_unpack(shadow_page_pack(shadow_page_unpack(value))));
EXPECT_EQ(shadow_lod_offset_unpack(value),
shadow_lod_offset_unpack(shadow_lod_offset_pack(shadow_lod_offset_unpack(value))));
ShadowTileData expected_tile = shadow_tile_unpack(value);
ShadowTileData result_tile = shadow_tile_unpack(shadow_tile_pack(expected_tile));
EXPECT_EQ(expected_tile.page, result_tile.page);
EXPECT_EQ(expected_tile.cache_index, result_tile.cache_index);
EXPECT_EQ(expected_tile.is_used, result_tile.is_used);
EXPECT_EQ(expected_tile.do_update, result_tile.do_update);
EXPECT_EQ(expected_tile.is_allocated, result_tile.is_allocated);
EXPECT_EQ(expected_tile.is_rendered, result_tile.is_rendered);
EXPECT_EQ(expected_tile.is_cached, result_tile.is_cached);
ShadowSamplingTile expected_sampling_tile = shadow_sampling_tile_unpack(value);
ShadowSamplingTile result_sampling_tile = shadow_sampling_tile_unpack(
shadow_sampling_tile_pack(expected_sampling_tile));
EXPECT_EQ(expected_sampling_tile.page, result_sampling_tile.page);
EXPECT_EQ(expected_sampling_tile.lod, result_sampling_tile.lod);
EXPECT_EQ(expected_sampling_tile.lod_offset, result_sampling_tile.lod_offset);
EXPECT_EQ(expected_sampling_tile.is_valid, result_sampling_tile.is_valid);
}
}
DRAW_TEST(eevee_shadow_tile_packing)
static void test_eevee_shadow_tilemap_amend()
{
GPU_render_begin();
blender::Vector<uint32_t> tilemap_data(SHADOW_TILEMAP_RES * SHADOW_TILEMAP_RES *
SHADOW_TILEMAP_PER_ROW);
tilemap_data.fill(0);
auto pixel_get = [&](int x, int y, int tilemap_index) -> uint32_t & {
/* Note: assumes that tilemap_index is < SHADOW_TILEMAP_PER_ROW. */
return tilemap_data[y * SHADOW_TILEMAP_RES * SHADOW_TILEMAP_PER_ROW + x +
tilemap_index * SHADOW_TILEMAP_RES];
};
ShadowSamplingTile tile;
tile.lod = 0;
tile.lod_offset = uint2(0);
tile.is_valid = true;
tile.page = uint3(1, 0, 0);
pixel_get(16, 16, 2) = shadow_sampling_tile_pack(tile);
tile.page = uint3(2, 0, 0);
pixel_get(17, 16, 2) = shadow_sampling_tile_pack(tile);
tile.page = uint3(3, 0, 0);
pixel_get(20, 20, 1) = shadow_sampling_tile_pack(tile);
tile.page = uint3(4, 0, 0);
pixel_get(17, 16, 0) = shadow_sampling_tile_pack(tile);
Texture tilemap_tx = {"tilemap_tx"};
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_HOST_READ | GPU_TEXTURE_USAGE_SHADER_READ |
GPU_TEXTURE_USAGE_SHADER_WRITE;
int2 tilemap_res(SHADOW_TILEMAP_RES * SHADOW_TILEMAP_PER_ROW, SHADOW_TILEMAP_RES);
tilemap_tx.ensure_2d(GPU_R32UI, tilemap_res, usage);
GPU_texture_update_sub(
tilemap_tx, GPU_DATA_UINT, tilemap_data.data(), 0, 0, 0, tilemap_res.x, tilemap_res.y, 0);
/* Setup one directional light with 3 tilemaps. Fill only the needed data. */
LightData light;
light.type = LIGHT_SUN;
light.sun.clipmap_lod_min = 0;
light.sun.clipmap_lod_max = 2;
/* Shift LOD0 by 1 tile towards bottom. */
light.sun.clipmap_base_offset_neg = int2(0, 1 << 0);
/* Shift LOD1 by 1 tile towards right. */
light.sun.clipmap_base_offset_pos = int2(1 << 1, 0);
light.tilemap_index = 0;
LightDataBuf culling_light_buf = {"Lights_culled"};
culling_light_buf[0] = light;
culling_light_buf.push_update();
LightCullingDataBuf culling_data_buf = {"LightCull_data"};
culling_data_buf.local_lights_len = 0;
culling_data_buf.sun_lights_len = 1;
culling_data_buf.items_count = 1;
culling_data_buf.push_update();
/* Needed for validation. But not used since we use directionals. */
LightCullingZbinBuf culling_zbin_buf = {"LightCull_zbin"};
LightCullingTileBuf culling_tile_buf = {"LightCull_tile"};
GPUShader *sh = GPU_shader_create_from_info_name("eevee_shadow_tilemap_amend");
PassSimple pass("Test");
pass.shader_set(sh);
pass.bind_image("tilemaps_img", tilemap_tx);
pass.bind_ssbo(LIGHT_CULL_BUF_SLOT, culling_data_buf);
pass.bind_ssbo(LIGHT_BUF_SLOT, culling_light_buf);
pass.bind_ssbo(LIGHT_ZBIN_BUF_SLOT, culling_zbin_buf);
pass.bind_ssbo(LIGHT_TILE_BUF_SLOT, culling_tile_buf);
pass.dispatch(int3(1));
pass.barrier(GPU_BARRIER_TEXTURE_UPDATE);
Manager manager;
manager.submit(pass);
{
uint *pixels = tilemap_tx.read<uint32_t>(GPU_DATA_UINT);
auto stringify_tilemap = [&](int tilemap_index) -> std::string {
std::string result = "";
for (auto y : IndexRange(SHADOW_TILEMAP_RES)) {
for (auto x : IndexRange(SHADOW_TILEMAP_RES)) {
/* Note: assumes that tilemap_index is < SHADOW_TILEMAP_PER_ROW. */
int tile_ofs = y * SHADOW_TILEMAP_RES * SHADOW_TILEMAP_PER_ROW + x +
tilemap_index * SHADOW_TILEMAP_RES;
ShadowSamplingTile tile = shadow_sampling_tile_unpack(pixels[tile_ofs]);
result += std::to_string(tile.page.x + tile.page.y * SHADOW_PAGE_PER_ROW);
if (x + 1 == SHADOW_TILEMAP_RES / 2) {
result += " ";
}
}
result += "\n";
if (y + 1 == SHADOW_TILEMAP_RES / 2) {
result += "\n";
}
}
return result;
};
auto stringify_lod = [&](int tilemap_index) -> std::string {
std::string result = "";
for (auto y : IndexRange(SHADOW_TILEMAP_RES)) {
for (auto x : IndexRange(SHADOW_TILEMAP_RES)) {
/* Note: assumes that tilemap_index is < SHADOW_TILEMAP_PER_ROW. */
int tile_ofs = y * SHADOW_TILEMAP_RES * SHADOW_TILEMAP_PER_ROW + x +
tilemap_index * SHADOW_TILEMAP_RES;
ShadowSamplingTile tile = shadow_sampling_tile_unpack(pixels[tile_ofs]);
result += std::to_string(tile.lod);
if (x + 1 == SHADOW_TILEMAP_RES / 2) {
result += " ";
}
}
result += "\n";
if (y + 1 == SHADOW_TILEMAP_RES / 2) {
result += "\n";
}
}
return result;
};
auto stringify_offset = [&](int tilemap_index) -> std::string {
std::string result = "";
for (auto y : IndexRange(SHADOW_TILEMAP_RES)) {
for (auto x : IndexRange(SHADOW_TILEMAP_RES)) {
/* Note: assumes that tilemap_index is < SHADOW_TILEMAP_PER_ROW. */
int tile_ofs = y * SHADOW_TILEMAP_RES * SHADOW_TILEMAP_PER_ROW + x +
tilemap_index * SHADOW_TILEMAP_RES;
ShadowSamplingTile tile = shadow_sampling_tile_unpack(pixels[tile_ofs]);
result += std::to_string(tile.lod_offset.x + tile.lod_offset.y);
if (x + 1 == SHADOW_TILEMAP_RES / 2) {
result += " ";
}
}
result += "\n";
if (y + 1 == SHADOW_TILEMAP_RES / 2) {
result += "\n";
}
}
return result;
};
/** The layout of these expected strings is Y down. */
StringRefNull expected_pages_lod2 =
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"\n"
"0000000000000000 1200000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n";
StringRefNull expected_pages_lod1 =
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"\n"
"0000000000000001 1220000000000000\n"
"0000000000000001 1220000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000300000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n";
StringRefNull expected_pages_lod0 =
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"\n"
"0000000000000000 0400000000000000\n"
"0000000000000011 1122220000000000\n"
"0000000000000011 1122220000000000\n"
"0000000000000011 1122220000000000\n"
"0000000000000011 1122220000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000033000000\n"
"0000000000000000 0000000033000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n";
EXPECT_EQ(expected_pages_lod2, stringify_tilemap(2));
EXPECT_EQ(expected_pages_lod1, stringify_tilemap(1));
EXPECT_EQ(expected_pages_lod0, stringify_tilemap(0));
StringRefNull expected_lod_lod0 =
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"\n"
"0000000000000000 0000000000000000\n"
"0000000000000022 2222220000000000\n"
"0000000000000022 2222220000000000\n"
"0000000000000022 2222220000000000\n"
"0000000000000022 2222220000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000011000000\n"
"0000000000000000 0000000011000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n";
EXPECT_EQ(expected_lod_lod0, stringify_lod(0));
/* Offset for each axis are added together in this test. */
StringRefNull expected_offset_lod0 =
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"\n"
"0000000000000000 0000000000000000\n"
"0000000000000055 5555550000000000\n"
"0000000000000055 5555550000000000\n"
"0000000000000055 5555550000000000\n"
"0000000000000055 5555550000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000011000000\n"
"0000000000000000 0000000011000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n"
"0000000000000000 0000000000000000\n";
EXPECT_EQ(expected_offset_lod0, stringify_offset(0));
MEM_SAFE_FREE(pixels);
}
GPU_shader_free(sh);
DRW_shaders_free();
GPU_render_end();
}
DRAW_TEST(eevee_shadow_tilemap_amend)
static void test_eevee_shadow_page_mask_ex(int max_view_per_tilemap)
{
GPU_render_begin();