EEVEE-Next: Shadow: Add LOD system to directional clipmap shadows #120031
|
@ -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
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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. */
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue