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
@ComputeShader V_ClearDecalsCS
@ComputeShader V_ClearCellsCS
@ComputeShader V_ClearParticlesCS
@ComputeShader V_BackdropCS
@VertexShader V_DQuadVS

View File

@ -309,7 +309,7 @@ void V_TickForever(WaveLaneCtx *lane)
world->tiles = PushStructs(world_arena, u8, S_TilesCount);
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 = Mebi(1);
@ -320,11 +320,13 @@ void V_TickForever(WaveLaneCtx *lane)
G_ResourceHandle gpu_state = Zi;
G_ResourceHandle gpu_tiles = 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_Texture2DRef gpu_tiles_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);
{
@ -338,7 +340,6 @@ void V_TickForever(WaveLaneCtx *lane)
);
gpu_state_ref = G_PushRWStructuredBufferRef(gpu_perm, gpu_state, V_GpuState);
}
// Init tile map texture
{
gpu_tiles = G_PushTexture2D(
@ -360,18 +361,31 @@ void V_TickForever(WaveLaneCtx *lane)
);
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,
// G_Format_R8_Uint,
// G_Format_R11G11B10_Float,
G_Format_R10G10B10A2_Unorm,
decals_dims,
cells_dims,
G_Layout_DirectQueue_ShaderReadWrite,
.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);
@ -876,14 +890,14 @@ void V_TickForever(WaveLaneCtx *lane)
frame->xf.draw_to_world = InvertXform(frame->xf.world_to_draw);
}
// World <-> decal
// TODO: This can be constant (so shaders don't need to read it every frame)
frame->xf.world_to_decal = XformIdentity;
frame->xf.decal_to_world = XformIdentity;
// World <-> cell
// TODO: This never changes, should be #defined (so shaders don't need to read it every frame)
frame->xf.world_to_cell = 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_decal = TranslateXform(frame->xf.world_to_decal, VEC2((S_WorldPitch / 2.0), (S_WorldPitch / 2.0)));
frame->xf.decal_to_world = InvertXform(frame->xf.world_to_decal);
frame->xf.world_to_cell = ScaleXform(frame->xf.world_to_cell, VEC2(V_CellsPerMeter, V_CellsPerMeter));
frame->xf.world_to_cell = TranslateXform(frame->xf.world_to_cell, VEC2((S_WorldPitch / 2.0), (S_WorldPitch / 2.0)));
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);
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);
{
UI_BuildLabelF("Camera pos: %F", FmtFloat2(frame->camera_pos));
UI_BuildLabelF("Cursor world pos: %F", FmtFloat2(frame->world_cursor));
UI_BuildLabelF("Cursor tile pos: %F", FmtSint2(tile_pos));
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_BuildSpacer(UI_PIX(padding, 1), Axis_Y);
@ -2356,7 +2370,8 @@ void V_TickForever(WaveLaneCtx *lane)
//////////////////////////////
//- 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;
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;
case V_CmdKind_clear_decals:
case V_CmdKind_clear_stains:
{
should_clear_decals = 1;
should_clear_stains = 1;
} break;
case V_CmdKind_clear_particles:
@ -2618,7 +2633,7 @@ void V_TickForever(WaveLaneCtx *lane)
frame->emitters_count += 1;
emitter = &en->emitter;
}
V_ParticleFlag flags = 0;
V_ParticleFlag flags = V_ParticleFlag_StainOnPrune;
emitter->flags = flags;
Vec2 dir = victim_raycast.normal;
@ -2628,16 +2643,16 @@ void V_TickForever(WaveLaneCtx *lane)
// emitter->count = 100;
// emitter->count = 500;
emitter->speed = 20;
emitter->speed_spread = 40;
emitter->speed = 10;
emitter->speed_spread = 20;
emitter->velocity_falloff = 30;
emitter->velocity_falloff_spread = 60;
emitter->angle = AngleFromVec2(dir);
// emitter->angle_spread = Tau / 4;
// emitter->angle_spread = Tau / 20;
emitter->angle_spread = Tau / 32;
emitter->angle_spread = Tau / 20;
// emitter->angle_spread = Tau / 32;
emitter->color_lin = LinearFromSrgb(VEC4(0.5, 0.1, 0.1, 1));
emitter->color_spread = VEC3(0.1, 0, 0);
@ -2874,7 +2889,9 @@ void V_TickForever(WaveLaneCtx *lane)
params.max_particles = max_particles;
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_StructuredBufferRef gpu_params_ref = G_PushStructuredBufferRef(frame->gpu_arena, gpu_params, V_GpuParams);
@ -2904,11 +2921,8 @@ void V_TickForever(WaveLaneCtx *lane)
//////////////////////////////
//- Setup pass
// Clear decals
if (should_clear_decals)
{
G_Compute(frame->cl, V_ClearDecalsCS, V_ThreadGroupSizeFromTexSize(decals_dims));
}
// Clear cells
G_Compute(frame->cl, V_ClearCellsCS, V_ThreadGroupSizeFromTexSize(cells_dims));
// Clear particles
if (should_clear_particles)
@ -2935,7 +2949,7 @@ void V_TickForever(WaveLaneCtx *lane)
// Simulate 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);
}

View File

@ -15,7 +15,7 @@
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(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 ), ) \
/* -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */

View File

@ -1,15 +1,20 @@
////////////////////////////////////////////////////////////
//~ Utility shaders
//- Clear decals
ComputeShader2D(V_ClearDecalsCS, 8, 8)
//- Clear cells
ComputeShader2D(V_ClearCellsCS, 8, 8)
{
V_GpuParams params = G_Dereference<V_GpuParams>(V_ShaderConst_Params)[0];
RWTexture2D<Vec4> decals = G_Dereference<Vec4>(params.decals);
Vec2 decal_idx = SV_DispatchThreadID;
if (decal_idx.x < countof(decals).x && decal_idx.y < countof(decals).y)
RWTexture2D<Vec4> cells = G_Dereference<Vec4>(params.cells);
RWTexture2D<Vec4> stains = G_Dereference<Vec4>(params.stains);
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
// Decals test
// Cells test
{
RWTexture2D<Vec4> decals = G_Dereference<Vec4>(params.decals);
Vec2 decal_pos = mul(params.xf.world_to_decal, Vec3(world_pos, 1));
// Vec2 decal_uv = decal_pos / countof(decals);
Vec4 decal = decals.Load(decal_pos);
if (decal.a != 0)
RWTexture2D<Vec4> cells = G_Dereference<Vec4>(params.cells);
Vec2 cell_pos = floor(mul(params.xf.world_to_cell, Vec3(world_pos, 1)));
Vec4 cell = cells.Load(cell_pos);
if (cell.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
// Decals test
// Stains 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);
// RWTexture2D<V_ParticleKind> stains = G_Dereference<V_ParticleKind>(params.stains);
// Vec2 cell_pos = mul(params.xf.world_to_cell, Vec3(world_pos, 1));
// V_ParticleKind stain = stains.Load(cell_pos);
// if (decal == V_ParticleKind_Test)
// if (stain == V_ParticleKind_Test)
// {
// // result = Color_Yellow;
// // 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];
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;
if (particle_idx < params.max_particles)
@ -319,22 +333,35 @@ ComputeShader(V_SimParticlesCS, 64)
{
particle.pos += particle.velocity * 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 (decal_pos.x >= 0 && decal_pos.y >= 0 && decal_pos.x < countof(decals).x && decal_pos.y < countof(decals).y)
if (particle.exists < 0.0001)
{
particle.exists = 0;
}
}
// Commit
{
// FIXME: Atomic write
decals[floor(decal_pos)] = Vec4(particle.color, 1);
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
{
// Prune out of bounds particle
particle.exists = 0;
}
if (particle.exists < 0.0001)
{
particle.exists = 0;
}
}
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;
output.sv_target0 = result;
return output;

View File

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

View File

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