separate decals into stains & cells

This commit is contained in:
jacob 2026-01-07 00:36:58 -06:00
parent 869b415ebb
commit f30ed79e91
6 changed files with 117 additions and 88 deletions

View File

@ -15,7 +15,7 @@
@EmbedDir V_Resources pp_vis_res @EmbedDir V_Resources pp_vis_res
@ComputeShader V_ClearDecalsCS @ComputeShader V_ClearCellsCS
@ComputeShader V_ClearParticlesCS @ComputeShader V_ClearParticlesCS
@ComputeShader V_BackdropCS @ComputeShader V_BackdropCS
@VertexShader V_DQuadVS @VertexShader V_DQuadVS

View File

@ -309,7 +309,7 @@ void V_TickForever(WaveLaneCtx *lane)
world->tiles = PushStructs(world_arena, u8, S_TilesCount); world->tiles = PushStructs(world_arena, u8, S_TilesCount);
Vec2I32 tiles_dims = VEC2I32(S_TilesPitch, S_TilesPitch); Vec2I32 tiles_dims = VEC2I32(S_TilesPitch, S_TilesPitch);
Vec2I32 decals_dims = VEC2I32(V_PixelsPerMeter * S_WorldPitch, V_PixelsPerMeter * S_WorldPitch); Vec2I32 cells_dims = VEC2I32(V_CellsPerMeter * S_WorldPitch, V_CellsPerMeter * S_WorldPitch);
// u32 max_particles = Kibi(128); // u32 max_particles = Kibi(128);
u32 max_particles = Mebi(1); u32 max_particles = Mebi(1);
@ -320,11 +320,13 @@ void V_TickForever(WaveLaneCtx *lane)
G_ResourceHandle gpu_state = Zi; G_ResourceHandle gpu_state = Zi;
G_ResourceHandle gpu_tiles = Zi; G_ResourceHandle gpu_tiles = Zi;
G_ResourceHandle gpu_particles = Zi; G_ResourceHandle gpu_particles = Zi;
G_ResourceHandle gpu_decals = Zi; G_ResourceHandle gpu_cells = Zi;
G_ResourceHandle gpu_stains = Zi;
G_RWStructuredBufferRef gpu_state_ref = Zi; G_RWStructuredBufferRef gpu_state_ref = Zi;
G_Texture2DRef gpu_tiles_ref = Zi; G_Texture2DRef gpu_tiles_ref = Zi;
G_RWStructuredBufferRef gpu_particles_ref = Zi; G_RWStructuredBufferRef gpu_particles_ref = Zi;
G_RWTexture2DRef gpu_decals_ref = Zi; G_RWTexture2DRef gpu_cells_ref = Zi;
G_RWTexture2DRef gpu_stains_ref = Zi;
{ {
G_CommandListHandle cl = G_PrepareCommandList(G_QueueKind_Direct); G_CommandListHandle cl = G_PrepareCommandList(G_QueueKind_Direct);
{ {
@ -338,7 +340,6 @@ void V_TickForever(WaveLaneCtx *lane)
); );
gpu_state_ref = G_PushRWStructuredBufferRef(gpu_perm, gpu_state, V_GpuState); gpu_state_ref = G_PushRWStructuredBufferRef(gpu_perm, gpu_state, V_GpuState);
} }
// Init tile map texture // Init tile map texture
{ {
gpu_tiles = G_PushTexture2D( gpu_tiles = G_PushTexture2D(
@ -360,18 +361,31 @@ void V_TickForever(WaveLaneCtx *lane)
); );
gpu_particles_ref = G_PushRWStructuredBufferRef(gpu_perm, gpu_particles, V_Particle); gpu_particles_ref = G_PushRWStructuredBufferRef(gpu_perm, gpu_particles, V_Particle);
} }
// Init decal texture // Init cells texture
{ {
gpu_decals = G_PushTexture2D( gpu_cells = G_PushTexture2D(
gpu_perm, cl, gpu_perm, cl,
// G_Format_R8_Uint, // G_Format_R8_Uint,
// G_Format_R11G11B10_Float, // G_Format_R11G11B10_Float,
G_Format_R10G10B10A2_Unorm, G_Format_R10G10B10A2_Unorm,
decals_dims, cells_dims,
G_Layout_DirectQueue_ShaderReadWrite, G_Layout_DirectQueue_ShaderReadWrite,
.flags = G_ResourceFlag_ZeroMemory | G_ResourceFlag_AllowShaderReadWrite .flags = G_ResourceFlag_ZeroMemory | G_ResourceFlag_AllowShaderReadWrite
); );
gpu_decals_ref = G_PushRWTexture2DRef(gpu_perm, gpu_decals); gpu_cells_ref = G_PushRWTexture2DRef(gpu_perm, gpu_cells);
}
// Init stain texture
{
gpu_stains = G_PushTexture2D(
gpu_perm, cl,
// G_Format_R8_Uint,
// G_Format_R11G11B10_Float,
G_Format_R10G10B10A2_Unorm,
cells_dims,
G_Layout_DirectQueue_ShaderReadWrite,
.flags = G_ResourceFlag_ZeroMemory | G_ResourceFlag_AllowShaderReadWrite
);
gpu_stains_ref = G_PushRWTexture2DRef(gpu_perm, gpu_stains);
} }
} }
G_CommitCommandList(cl); G_CommitCommandList(cl);
@ -876,14 +890,14 @@ void V_TickForever(WaveLaneCtx *lane)
frame->xf.draw_to_world = InvertXform(frame->xf.world_to_draw); frame->xf.draw_to_world = InvertXform(frame->xf.world_to_draw);
} }
// World <-> decal // World <-> cell
// TODO: This can be constant (so shaders don't need to read it every frame) // TODO: This never changes, should be #defined (so shaders don't need to read it every frame)
frame->xf.world_to_decal = XformIdentity; frame->xf.world_to_cell = XformIdentity;
frame->xf.decal_to_world = XformIdentity; frame->xf.cell_to_world = XformIdentity;
{ {
frame->xf.world_to_decal = ScaleXform(frame->xf.world_to_decal, VEC2(V_PixelsPerMeter, V_PixelsPerMeter)); frame->xf.world_to_cell = ScaleXform(frame->xf.world_to_cell, VEC2(V_CellsPerMeter, V_CellsPerMeter));
frame->xf.world_to_decal = TranslateXform(frame->xf.world_to_decal, VEC2((S_WorldPitch / 2.0), (S_WorldPitch / 2.0))); frame->xf.world_to_cell = TranslateXform(frame->xf.world_to_cell, VEC2((S_WorldPitch / 2.0), (S_WorldPitch / 2.0)));
frame->xf.decal_to_world = InvertXform(frame->xf.world_to_decal); frame->xf.cell_to_world = InvertXform(frame->xf.world_to_cell);
} }
////////////////////////////// //////////////////////////////
@ -2168,14 +2182,14 @@ void V_TickForever(WaveLaneCtx *lane)
{ {
UI_BuildSpacer(UI_PIX(padding, 1), Axis_Y); UI_BuildSpacer(UI_PIX(padding, 1), Axis_Y);
Vec2I32 tile_pos = S_TilePosFromWorldPos(frame->world_cursor); Vec2I32 tile_pos = S_TilePosFromWorldPos(frame->world_cursor);
Vec2 decal_pos = MulXformV2(frame->xf.world_to_decal, frame->world_cursor); Vec2 cell_pos = MulXformV2(frame->xf.world_to_cell, frame->world_cursor);
i32 tile_idx = S_TileIdxFromTilePos(tile_pos); i32 tile_idx = S_TileIdxFromTilePos(tile_pos);
{ {
UI_BuildLabelF("Camera pos: %F", FmtFloat2(frame->camera_pos)); UI_BuildLabelF("Camera pos: %F", FmtFloat2(frame->camera_pos));
UI_BuildLabelF("Cursor world pos: %F", FmtFloat2(frame->world_cursor)); UI_BuildLabelF("Cursor world pos: %F", FmtFloat2(frame->world_cursor));
UI_BuildLabelF("Cursor tile pos: %F", FmtSint2(tile_pos)); UI_BuildLabelF("Cursor tile pos: %F", FmtSint2(tile_pos));
UI_BuildLabelF("Cursor tile idx: %F", FmtSint(tile_idx)); UI_BuildLabelF("Cursor tile idx: %F", FmtSint(tile_idx));
UI_BuildLabelF("Cursor decal pos: %F", FmtFloat2(decal_pos)); UI_BuildLabelF("Cursor cell pos: %F", FmtFloat2(cell_pos));
UI_BuildLabelF("Hovered ent: %F", S_FmtKey(hovered_ent->key)); UI_BuildLabelF("Hovered ent: %F", S_FmtKey(hovered_ent->key));
} }
UI_BuildSpacer(UI_PIX(padding, 1), Axis_Y); UI_BuildSpacer(UI_PIX(padding, 1), Axis_Y);
@ -2356,7 +2370,8 @@ void V_TickForever(WaveLaneCtx *lane)
////////////////////////////// //////////////////////////////
//- Process vis commands //- Process vis commands
b32 should_clear_decals = !TweakBool("Persist decals", 0); // b32 should_clear_stains = !TweakBool("Persist stains", 0);
b32 should_clear_stains = 0;
b32 should_clear_particles = 0; b32 should_clear_particles = 0;
for (V_CmdNode *cmd_node = frame->first_cmd_node; cmd_node; cmd_node = cmd_node->next) for (V_CmdNode *cmd_node = frame->first_cmd_node; cmd_node; cmd_node = cmd_node->next)
{ {
@ -2495,9 +2510,9 @@ void V_TickForever(WaveLaneCtx *lane)
} }
} break; } break;
case V_CmdKind_clear_decals: case V_CmdKind_clear_stains:
{ {
should_clear_decals = 1; should_clear_stains = 1;
} break; } break;
case V_CmdKind_clear_particles: case V_CmdKind_clear_particles:
@ -2618,7 +2633,7 @@ void V_TickForever(WaveLaneCtx *lane)
frame->emitters_count += 1; frame->emitters_count += 1;
emitter = &en->emitter; emitter = &en->emitter;
} }
V_ParticleFlag flags = 0; V_ParticleFlag flags = V_ParticleFlag_StainOnPrune;
emitter->flags = flags; emitter->flags = flags;
Vec2 dir = victim_raycast.normal; Vec2 dir = victim_raycast.normal;
@ -2628,16 +2643,16 @@ void V_TickForever(WaveLaneCtx *lane)
// emitter->count = 100; // emitter->count = 100;
// emitter->count = 500; // emitter->count = 500;
emitter->speed = 20; emitter->speed = 10;
emitter->speed_spread = 40; emitter->speed_spread = 20;
emitter->velocity_falloff = 30; emitter->velocity_falloff = 30;
emitter->velocity_falloff_spread = 60; emitter->velocity_falloff_spread = 60;
emitter->angle = AngleFromVec2(dir); emitter->angle = AngleFromVec2(dir);
// emitter->angle_spread = Tau / 4; // emitter->angle_spread = Tau / 4;
// emitter->angle_spread = Tau / 20; emitter->angle_spread = Tau / 20;
emitter->angle_spread = Tau / 32; // emitter->angle_spread = Tau / 32;
emitter->color_lin = LinearFromSrgb(VEC4(0.5, 0.1, 0.1, 1)); emitter->color_lin = LinearFromSrgb(VEC4(0.5, 0.1, 0.1, 1));
emitter->color_spread = VEC3(0.1, 0, 0); emitter->color_spread = VEC3(0.1, 0, 0);
@ -2874,7 +2889,9 @@ void V_TickForever(WaveLaneCtx *lane)
params.max_particles = max_particles; params.max_particles = max_particles;
params.particles = gpu_particles_ref; params.particles = gpu_particles_ref;
params.decals = gpu_decals_ref; params.should_clear_stains = should_clear_stains;
params.cells = gpu_cells_ref;
params.stains = gpu_stains_ref;
} }
G_ResourceHandle gpu_params = G_PushBufferFromCpuCopy(frame->gpu_arena, frame->cl, StringFromStruct(&params)); G_ResourceHandle gpu_params = G_PushBufferFromCpuCopy(frame->gpu_arena, frame->cl, StringFromStruct(&params));
G_StructuredBufferRef gpu_params_ref = G_PushStructuredBufferRef(frame->gpu_arena, gpu_params, V_GpuParams); G_StructuredBufferRef gpu_params_ref = G_PushStructuredBufferRef(frame->gpu_arena, gpu_params, V_GpuParams);
@ -2904,11 +2921,8 @@ void V_TickForever(WaveLaneCtx *lane)
////////////////////////////// //////////////////////////////
//- Setup pass //- Setup pass
// Clear decals // Clear cells
if (should_clear_decals) G_Compute(frame->cl, V_ClearCellsCS, V_ThreadGroupSizeFromTexSize(cells_dims));
{
G_Compute(frame->cl, V_ClearDecalsCS, V_ThreadGroupSizeFromTexSize(decals_dims));
}
// Clear particles // Clear particles
if (should_clear_particles) if (should_clear_particles)
@ -2935,7 +2949,7 @@ void V_TickForever(WaveLaneCtx *lane)
// Simulate particles // Simulate particles
G_Compute(frame->cl, V_SimParticlesCS, V_ThreadGroupSizeFromBufferSize(max_particles)); G_Compute(frame->cl, V_SimParticlesCS, V_ThreadGroupSizeFromBufferSize(max_particles));
// Barrier since decals were written // Barrier since stains were written
G_DumbGlobalMemorySync(frame->cl); G_DumbGlobalMemorySync(frame->cl);
} }

View File

@ -15,7 +15,7 @@
X(spawn, Spawn/Teleport Player, V_CmdDescFlag_None, V_HOTKEY( Button_T ), ) \ X(spawn, Spawn/Teleport Player, V_CmdDescFlag_None, V_HOTKEY( Button_T ), ) \
X(spawn_dummy, Spawn Dummy, V_CmdDescFlag_None, V_HOTKEY( Button_R ), ) \ X(spawn_dummy, Spawn Dummy, V_CmdDescFlag_None, V_HOTKEY( Button_R ), ) \
X(delete, Delete entity at cursor, V_CmdDescFlag_None, V_HOTKEY( Button_M2 ), ) \ X(delete, Delete entity at cursor, V_CmdDescFlag_None, V_HOTKEY( Button_M2 ), ) \
X(clear_decals, Clear decals, V_CmdDescFlag_None, V_HOTKEY( Button_C, .alt = 1 ), ) \ X(clear_stains, Clear stains, V_CmdDescFlag_None, V_HOTKEY( Button_C, .alt = 1 ), ) \
X(clear_particles, Clear particles, V_CmdDescFlag_None, V_HOTKEY( Button_C ), ) \ X(clear_particles, Clear particles, V_CmdDescFlag_None, V_HOTKEY( Button_C ), ) \
/* -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */

View File

@ -1,15 +1,20 @@
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ Utility shaders //~ Utility shaders
//- Clear decals //- Clear cells
ComputeShader2D(V_ClearDecalsCS, 8, 8) ComputeShader2D(V_ClearCellsCS, 8, 8)
{ {
V_GpuParams params = G_Dereference<V_GpuParams>(V_ShaderConst_Params)[0]; V_GpuParams params = G_Dereference<V_GpuParams>(V_ShaderConst_Params)[0];
RWTexture2D<Vec4> decals = G_Dereference<Vec4>(params.decals); RWTexture2D<Vec4> cells = G_Dereference<Vec4>(params.cells);
Vec2 decal_idx = SV_DispatchThreadID; RWTexture2D<Vec4> stains = G_Dereference<Vec4>(params.stains);
if (decal_idx.x < countof(decals).x && decal_idx.y < countof(decals).y) Vec2 cells_idx = SV_DispatchThreadID;
if (cells_idx.x < countof(cells).x && cells_idx.y < countof(cells).y)
{ {
decals[decal_idx] = 0; cells[cells_idx] = 0;
if (params.should_clear_stains)
{
stains[cells_idx] = 0;
}
} }
} }
@ -148,30 +153,38 @@ ComputeShader2D(V_BackdropCS, 8, 8)
// TODO: Remove this // TODO: Remove this
// Decals test // Cells test
{ {
RWTexture2D<Vec4> decals = G_Dereference<Vec4>(params.decals); RWTexture2D<Vec4> cells = G_Dereference<Vec4>(params.cells);
Vec2 decal_pos = mul(params.xf.world_to_decal, Vec3(world_pos, 1)); Vec2 cell_pos = floor(mul(params.xf.world_to_cell, Vec3(world_pos, 1)));
// Vec2 decal_uv = decal_pos / countof(decals); Vec4 cell = cells.Load(cell_pos);
Vec4 decal = decals.Load(decal_pos); if (cell.a != 0)
if (decal.a != 0)
{ {
result = decal; result = cell;
}
else
{
RWTexture2D<Vec4> stains = G_Dereference<Vec4>(params.stains);
Vec4 stain = stains.Load(cell_pos);
if (stain.a != 0)
{
result = stain;
}
} }
} }
// TODO: Remove this // TODO: Remove this
// Decals test // Stains test
// { // {
// RWTexture2D<V_ParticleKind> decals = G_Dereference<V_ParticleKind>(params.decals); // RWTexture2D<V_ParticleKind> stains = G_Dereference<V_ParticleKind>(params.stains);
// Vec2 decal_pos = mul(params.xf.world_to_decal, Vec3(world_pos, 1)); // Vec2 cell_pos = mul(params.xf.world_to_cell, Vec3(world_pos, 1));
// // Vec2 decal_uv = decal_pos / countof(decals); // V_ParticleKind stain = stains.Load(cell_pos);
// V_ParticleKind decal = decals.Load(decal_pos);
// if (decal == V_ParticleKind_Test) // if (stain == V_ParticleKind_Test)
// { // {
// // result = Color_Yellow; // // result = Color_Yellow;
// // result = LinearFromSrgb(Vec4(0.5, 0.1, 0.1, 1)); // // result = LinearFromSrgb(Vec4(0.5, 0.1, 0.1, 1));
@ -269,7 +282,8 @@ ComputeShader(V_SimParticlesCS, 64)
{ {
V_GpuParams params = G_Dereference<V_GpuParams>(V_ShaderConst_Params)[0]; V_GpuParams params = G_Dereference<V_GpuParams>(V_ShaderConst_Params)[0];
RWStructuredBuffer<V_Particle> particles = G_Dereference<V_Particle>(params.particles); RWStructuredBuffer<V_Particle> particles = G_Dereference<V_Particle>(params.particles);
RWTexture2D<Vec4> decals = G_Dereference<Vec4>(params.decals); RWTexture2D<Vec4> cells = G_Dereference<Vec4>(params.cells);
RWTexture2D<Vec4> stains = G_Dereference<Vec4>(params.stains);
u32 particle_idx = SV_DispatchThreadID; u32 particle_idx = SV_DispatchThreadID;
if (particle_idx < params.max_particles) if (particle_idx < params.max_particles)
@ -319,22 +333,35 @@ ComputeShader(V_SimParticlesCS, 64)
{ {
particle.pos += particle.velocity * params.dt; particle.pos += particle.velocity * params.dt;
particle.velocity = lerp(particle.velocity, 0, particle.velocity_falloff * params.dt); particle.velocity = lerp(particle.velocity, 0, particle.velocity_falloff * params.dt);
Vec2 decal_pos = mul(params.xf.world_to_decal, Vec3(particle.pos, 1)); if (particle.exists < 0.0001)
if (decal_pos.x >= 0 && decal_pos.y >= 0 && decal_pos.x < countof(decals).x && decal_pos.y < countof(decals).y)
{ {
// FIXME: Atomic write particle.exists = 0;
decals[floor(decal_pos)] = Vec4(particle.color, 1); }
}
// Commit
{
// FIXME: Atomic write
Vec2 cell_pos = floor(mul(params.xf.world_to_cell, Vec3(particle.pos, 1)));
if (cell_pos.x >= 0 && cell_pos.y >= 0 && cell_pos.x < countof(stains).x && cell_pos.y < countof(stains).y)
{
b32 should_stain = 0;
if (particle.flags & V_ParticleFlag_StainOnPrune && particle.exists == 0)
{
should_stain = 1;
}
cells[cell_pos] = Vec4(particle.color, 1);
if (should_stain)
{
stains[cell_pos] = Vec4(particle.color, 1);
}
} }
else else
{ {
// Prune out of bounds particle // Prune out of bounds particle
particle.exists = 0; particle.exists = 0;
} }
if (particle.exists < 0.0001)
{
particle.exists = 0;
}
} }
particles[particle_idx] = particle; particles[particle_idx] = particle;
} }
@ -447,22 +474,6 @@ PixelShader(V_OverlayPS, V_OverlayPSOutput, V_OverlayPSInput input)
} }
} }
// // TODO: Remove this
// // Decals test
// {
// RWTexture2D<V_ParticleKind> decals = G_Dereference<V_ParticleKind>(params.decals);
// Vec2 decal_pos = mul(params.xf.world_to_decal, Vec3(world_pos, 1));
// // Vec2 decal_uv = decal_pos / countof(decals);
// V_ParticleKind decal = decals.Load(decal_pos);
// if (decal == V_ParticleKind_Test)
// {
// result = Color_Yellow;
// // result = LinearFromSrgb(Vec4(0.5, 0.1, 0.1, 1));
// }
// }
V_OverlayPSOutput output; V_OverlayPSOutput output;
output.sv_target0 = result; output.sv_target0 = result;
return output; return output;

View File

@ -44,7 +44,7 @@ Struct(V_OverlayPSOutput)
//~ Shaders //~ Shaders
//- Utility shaders //- Utility shaders
ComputeShader2D(V_ClearDecalsCS, 8, 8); ComputeShader2D(V_ClearCellsCS, 8, 8);
ComputeShader(V_ClearParticlesCS, 64); ComputeShader(V_ClearParticlesCS, 64);
//- Backdrop shader //- Backdrop shader

View File

@ -1,5 +1,5 @@
#define V_PixelsPerMeter 48.0 #define V_CellsPerMeter 48.0
#define V_PixelsPerSqMeter (V_PixelsPerMeter * V_PixelsPerMeter) #define V_CellsPerSqMeter (V_CellsPerMeter * V_CellsPerMeter)
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ Constant types //~ Constant types
@ -28,9 +28,9 @@ Struct(V_Xforms)
Xform world_to_draw; Xform world_to_draw;
Xform draw_to_world; Xform draw_to_world;
// World <-> decal // World <-> cell
Xform world_to_decal; Xform world_to_cell;
Xform decal_to_world; Xform cell_to_world;
}; };
Struct(V_GpuState) Struct(V_GpuState)
@ -74,7 +74,10 @@ Struct(V_GpuParams)
u32 max_particles; u32 max_particles;
G_RWStructuredBufferRef particles; G_RWStructuredBufferRef particles;
G_RWTexture2DRef decals;
b32 should_clear_stains;
G_RWTexture2DRef cells;
G_RWTexture2DRef stains;
}; };
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
@ -82,7 +85,8 @@ Struct(V_GpuParams)
Enum(V_ParticleFlag) Enum(V_ParticleFlag)
{ {
V_ParticleFlag_None = (0), V_ParticleFlag_None = 0,
V_ParticleFlag_StainOnPrune = (1 << 0),
}; };
Struct(V_Emitter) Struct(V_Emitter)
@ -112,6 +116,7 @@ Struct(V_Particle)
V_ParticleFlag flags; V_ParticleFlag flags;
u32 emitter_init_num; // if != 0, then initialize using emitter at index (emitter_init_num - 1) u32 emitter_init_num; // if != 0, then initialize using emitter at index (emitter_init_num - 1)
u32 seq;
u32 idx_in_emitter; u32 idx_in_emitter;
Vec2 pos; Vec2 pos;
@ -137,7 +142,6 @@ Struct(V_Particle)
Enum(V_DQuadFlag) Enum(V_DQuadFlag)
{ {
V_DQuadFlag_None = 0, V_DQuadFlag_None = 0,
V_DQuadFlag_DrawGrid = (1 << 0),
}; };
Struct(V_DQuad) Struct(V_DQuad)