rename basic shader to character
buildbot/windows Build done.
buildbot/linux Build done.

add character shadow
make animation play twice as fast when sprinting
This commit is contained in:
Sven Balzer
2026-08-03 13:32:20 +02:00
parent 4f4ec3cfd5
commit 6aa530daea
11 changed files with 475 additions and 11 deletions
+2 -1
View File
@@ -105,7 +105,8 @@ function(add_shader name)
set(SHADERS_WGSL ${SHADERS_WGSL} "${CMAKE_CURRENT_SOURCE_DIR}/src/shaders/wgsl/${name}.wgsl" PARENT_SCOPE)
endfunction()
add_shader(basic)
add_shader(character)
add_shader(character_shadow)
add_shader(world)
add_shader(grid)
+24 -3
View File
@@ -36,6 +36,7 @@ static SDL_Window *window;
static R_Texture framebuffer;
static R_Texture player_texture;
static R_Texture character_shadow_texture;
static R_Texture tile_textures_atlas;
static R_Texture tile_textures_atlas_imgui;
@@ -915,6 +916,12 @@ static bool init_resources() {
return false;
}
character_shadow_texture = create_texture("characters/shadow.png");
if (!character_shadow_texture) {
log_error("Failed to create shader texture.");
return false;
}
if (!recreate_tile_textures()) {
log_error("Failed to create tile textures.");
return false;
@@ -1438,7 +1445,7 @@ static void update_state_game() {
const bool *keyboard_state = SDL_GetKeyboardState(NULL);
if (player.is_moving) {
player.animation_time += delta_time;
player.animation_time += delta_time * (keyboard_state[SDL_SCANCODE_LSHIFT] ? 2 : 1);
vec2 leftover_movement = vec2(player.target_position) - player.visual_position;
vec2 movement = clamp(min(vec2(0, 0), leftover_movement), normalize(leftover_movement) * (delta_t * character_speed * (keyboard_state[SDL_SCANCODE_LSHIFT] ? 2.0f : 1.0f)), max(vec2(0, 0), leftover_movement));
@@ -1478,7 +1485,8 @@ static void render_game() {
{
ZoneScopedN("player_instance_buffer");
Uint64 variation_index = (player.animation_time / (100 * SDL_NS_PER_MS)) % 4;
Uint64 ns_per_variation = (1.0f / (character_speed * 2)) * SDL_NS_PER_SECOND;
Uint64 variation_index = (player.animation_time / (ns_per_variation)) % 4;
player_instance.pos = player.visual_position;
player_instance.sprite_selection = variation_index << 16 | player.direction;
@@ -1555,7 +1563,20 @@ static void render_game() {
{
ZoneScopedN("Draw Player");
renderer_frame_set_shader(R_SHADER_BASIC);
renderer_frame_set_shader(R_SHADER_CHARACTER_SHADOW);
renderer_frame_draw(vertex_buffer, index_buffer, player_instance_buffer, 6, 1, &(R_Draw_Resources){
.vertex_uniform_buffers = (R_Buffer[]){ view_projection_matrix_buffer },
.num_vertex_uniform_buffers = 1,
.fragment_sampled_textures = (R_Texture[]){ character_shadow_texture },
.fragment_uniform_buffers = (R_Buffer[]){ tint_color_buffer },
.num_fragment_sampled_textures = 1,
.num_fragment_uniform_buffers = 1,
});
renderer_frame_set_shader(R_SHADER_CHARACTER);
renderer_frame_draw(vertex_buffer, index_buffer, player_instance_buffer, 6, 1, &(R_Draw_Resources){
.vertex_uniform_buffers = (R_Buffer[]){ view_projection_matrix_buffer },
+2 -1
View File
@@ -8,7 +8,8 @@ extern "C" {
#endif // __cplusplus
typedef enum : Uint32 {
R_SHADER_BASIC,
R_SHADER_CHARACTER,
R_SHADER_CHARACTER_SHADOW,
R_SHADER_WORLD,
R_SHADER_GRID,
R_SHADER_COUNT,
+160 -3
View File
@@ -409,9 +409,9 @@ bool renderer_init(SDL_Window *window_) {
});
// Shaders
{ // Basic
{ // Character
const Uint8 shader_source[] = {
#embed "shaders/spirv/basic.spv"
#embed "shaders/spirv/character.spv"
};
SDL_GPUShader *vertex_shader = SDL_CreateGPUShader(device, &(SDL_GPUShaderCreateInfo){
@@ -445,7 +445,164 @@ bool renderer_init(SDL_Window *window_) {
SDL_PropertiesID properties = SDL_CreateProperties();
SDL_SetStringProperty(properties, SDL_PROP_GPU_BUFFER_CREATE_NAME_STRING, "R_SHADER basic");
shaders[R_SHADER_BASIC] = SDL_CreateGPUGraphicsPipeline(device, &(SDL_GPUGraphicsPipelineCreateInfo){
shaders[R_SHADER_CHARACTER] = SDL_CreateGPUGraphicsPipeline(device, &(SDL_GPUGraphicsPipelineCreateInfo){
.vertex_shader = vertex_shader,
.fragment_shader = fragment_shader,
.vertex_input_state = {
.vertex_buffer_descriptions = (SDL_GPUVertexBufferDescription[]){
{
.slot = 0,
.pitch = 20,
.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX,
.instance_step_rate = 0,
},
{
.slot = 1,
.pitch = 12,
.input_rate = SDL_GPU_VERTEXINPUTRATE_INSTANCE,
.instance_step_rate = 0,
},
},
.num_vertex_buffers = 2,
.vertex_attributes = (SDL_GPUVertexAttribute[]){
{
.location = 0,
.buffer_slot = 0,
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
.offset = 0,
},
{
.location = 1,
.buffer_slot = 0,
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2,
.offset = 12,
},
{
.location = 2,
.buffer_slot = 1,
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2,
.offset = 0,
},
{
.location = 3,
.buffer_slot = 1,
.format = SDL_GPU_VERTEXELEMENTFORMAT_UINT,
.offset = 8,
},
},
.num_vertex_attributes = 4,
},
.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
.rasterizer_state = {
.fill_mode = SDL_GPU_FILLMODE_FILL,
.cull_mode = SDL_GPU_CULLMODE_BACK,
.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE,
.depth_bias_constant_factor = 0.0f,
.depth_bias_clamp = 0.0f,
.depth_bias_slope_factor = 0.0f,
.enable_depth_bias = false,
.enable_depth_clip = false,
},
.multisample_state = {
.sample_count = SDL_GPU_SAMPLECOUNT_8,
.sample_mask = 0,
.enable_mask = 0,
.enable_alpha_to_coverage = false,
},
.depth_stencil_state = {
.compare_op = SDL_GPU_COMPAREOP_GREATER_OR_EQUAL,
.back_stencil_state = { .fail_op = SDL_GPU_STENCILOP_INVALID, .pass_op = SDL_GPU_STENCILOP_INVALID, .depth_fail_op = SDL_GPU_STENCILOP_INVALID, .compare_op = SDL_GPU_COMPAREOP_INVALID, },
.front_stencil_state = { .fail_op = SDL_GPU_STENCILOP_INVALID, .pass_op = SDL_GPU_STENCILOP_INVALID, .depth_fail_op = SDL_GPU_STENCILOP_INVALID, .compare_op = SDL_GPU_COMPAREOP_INVALID, },
.compare_mask = 0,
.write_mask = 0,
.enable_depth_test = false,
.enable_depth_write = false,
.enable_stencil_test = false,
},
.target_info = {
.color_target_descriptions = (SDL_GPUColorTargetDescription[]){
{
.format = texture_formats[surface.format],
.blend_state = {
.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
.color_blend_op = SDL_GPU_BLENDOP_ADD,
.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
.alpha_blend_op = SDL_GPU_BLENDOP_ADD,
.color_write_mask = 0,
.enable_blend = true,
.enable_color_write_mask = false,
},
}
},
.num_color_targets = 1,
.depth_stencil_format = SDL_GPU_TEXTUREFORMAT_INVALID,
.has_depth_stencil_target = false,
},
});
SDL_DestroyProperties(properties);
SDL_ReleaseGPUShader(device, vertex_shader);
SDL_ReleaseGPUShader(device, fragment_shader);
}
{ // Character Shadow
const Uint8 shader_source[] = {
#embed "shaders/spirv/character_shadow.spv"
};
SDL_GPUShader *vertex_shader = SDL_CreateGPUShader(device, &(SDL_GPUShaderCreateInfo){
.code_size = sizeof(shader_source),
.code = shader_source,
.entrypoint = "main_vertex",
.format = SDL_GPU_SHADERFORMAT_SPIRV,
.stage = SDL_GPU_SHADERSTAGE_VERTEX,
.num_samplers = 0,
.num_storage_textures = 0,
.num_storage_buffers = 0,
.num_uniform_buffers = 1,
});
SDL_GPUShader *fragment_shader = SDL_CreateGPUShader(device, &(SDL_GPUShaderCreateInfo){
.code_size = sizeof(shader_source),
.code = shader_source,
.entrypoint = "main_fragment",
.format = SDL_GPU_SHADERFORMAT_SPIRV,
.stage = SDL_GPU_SHADERSTAGE_FRAGMENT,
.num_samplers = 1,
.num_storage_textures = 0,
.num_storage_buffers = 0,
.num_uniform_buffers = 1,
});
SDL_PropertiesID properties = SDL_CreateProperties();
SDL_SetStringProperty(properties, SDL_PROP_GPU_BUFFER_CREATE_NAME_STRING, "R_SHADER basic");
shaders[R_SHADER_CHARACTER_SHADOW] = SDL_CreateGPUGraphicsPipeline(device, &(SDL_GPUGraphicsPipelineCreateInfo){
.vertex_shader = vertex_shader,
.fragment_shader = fragment_shader,
+124 -3
View File
@@ -673,9 +673,9 @@ bool renderer_init(SDL_Window *window_) {
};
// Shaders
{ // Basic
{ // Character
const char shader_source[] = {
#embed "shaders/wgsl/basic.wgsl"
#embed "shaders/wgsl/character.wgsl"
};
WGPUShaderModule shader = wgpuDeviceCreateShaderModule(device, &(WGPUShaderModuleDescriptor){
@@ -686,7 +686,128 @@ bool renderer_init(SDL_Window *window_) {
.label = { .data = "basic shader", .length = WGPU_STRLEN }
});
shaders[R_SHADER_BASIC] = wgpuDeviceCreateRenderPipeline(device, &(WGPURenderPipelineDescriptor){
shaders[R_SHADER_CHARACTER] = wgpuDeviceCreateRenderPipeline(device, &(WGPURenderPipelineDescriptor){
.label = { .data = "basic render_pipeline", .length = WGPU_STRLEN },
.layout = wgpuDeviceCreatePipelineLayout(device, &(WGPUPipelineLayoutDescriptor){
.label = { .data = "basic pipeline_layout", .length = WGPU_STRLEN },
.bindGroupLayoutCount = 4,
.bindGroupLayouts = (WGPUBindGroupLayout[]){
wgpuDeviceCreateBindGroupLayout(device, &(WGPUBindGroupLayoutDescriptor){
.entryCount = 0,
.entries = (WGPUBindGroupLayoutEntry[]){
},
}),
wgpuDeviceCreateBindGroupLayout(device, &(WGPUBindGroupLayoutDescriptor){
.entryCount = 1,
.entries = (WGPUBindGroupLayoutEntry[]){
{ .binding = 0, .visibility = WGPUShaderStage_Vertex, .buffer = { .type = WGPUBufferBindingType_Uniform } },
},
}),
wgpuDeviceCreateBindGroupLayout(device, &(WGPUBindGroupLayoutDescriptor){
.entryCount = 2,
.entries = (WGPUBindGroupLayoutEntry[]){
{ .binding = 0, .visibility = WGPUShaderStage_Fragment, .texture = { .sampleType = WGPUTextureSampleType_Float, .viewDimension = WGPUTextureViewDimension_2D } },
{ .binding = 1, .visibility = WGPUShaderStage_Fragment, .sampler = { .type = WGPUSamplerBindingType_Filtering } },
},
}),
wgpuDeviceCreateBindGroupLayout(device, &(WGPUBindGroupLayoutDescriptor){
.entryCount = 1,
.entries = (WGPUBindGroupLayoutEntry[]){
{ .binding = 0, .visibility = WGPUShaderStage_Fragment, .buffer = { .type = WGPUBufferBindingType_Uniform } },
},
}),
},
}),
.vertex = {
.module = shader,
.entryPoint = { .data = "main_vertex", .length = WGPU_STRLEN },
.constantCount = 0,
.constants = NULL,
.bufferCount = 2,
.buffers = (WGPUVertexBufferLayout[]){
{
.stepMode = WGPUVertexStepMode_Vertex,
.arrayStride = 20,
.attributeCount = 2,
.attributes = (WGPUVertexAttribute[]){
{
.format = WGPUVertexFormat_Float32x3,
.offset = 0,
.shaderLocation = 0,
},
{
.format = WGPUVertexFormat_Float32x2,
.offset = 12,
.shaderLocation = 1,
},
},
},
{
.stepMode = WGPUVertexStepMode_Instance,
.arrayStride = 12,
.attributeCount = 2,
.attributes = (WGPUVertexAttribute[]){
{
.format = WGPUVertexFormat_Float32x2,
.offset = 0,
.shaderLocation = 2,
},
{
.format = WGPUVertexFormat_Uint32,
.offset = 8,
.shaderLocation = 3,
},
},
},
},
},
.primitive = {
.topology = WGPUPrimitiveTopology_TriangleList,
.stripIndexFormat = WGPUIndexFormat_Undefined,
.frontFace = WGPUFrontFace_CCW,
.cullMode = WGPUCullMode_Back,
.unclippedDepth = false,
},
.depthStencil = NULL,
.multisample = {
.count = 4,
.mask = ~0u,
.alphaToCoverageEnabled = false,
},
.fragment = &(WGPUFragmentState){
.module = shader,
.entryPoint = { .data = "main_fragment", .length = WGPU_STRLEN },
.constantCount = 0,
.constants = NULL,
.targetCount = 1,
.targets = &color_target_state,
},
});
wgpuShaderModuleRelease(shader);
}
{ // Character Shadow
const char shader_source[] = {
#embed "shaders/wgsl/character_shadow.wgsl"
};
WGPUShaderModule shader = wgpuDeviceCreateShaderModule(device, &(WGPUShaderModuleDescriptor){
.nextInChain = (WGPUChainedStruct *)&(WGPUShaderSourceWGSL){
.chain = { .next = NULL, .sType = WGPUSType_ShaderSourceWGSL },
.code = { .data = shader_source, .length = SDL_arraysize(shader_source) },
},
.label = { .data = "basic shader", .length = WGPU_STRLEN }
});
shaders[R_SHADER_CHARACTER_SHADOW] = wgpuDeviceCreateRenderPipeline(device, &(WGPURenderPipelineDescriptor){
.label = { .data = "basic render_pipeline", .length = WGPU_STRLEN },
.layout = wgpuDeviceCreatePipelineLayout(device, &(WGPUPipelineLayoutDescriptor){
Binary file not shown.
+75
View File
@@ -0,0 +1,75 @@
#language 2026
#include "shared.slang"
struct VertexShaderInput {
uint32_t vertex_index : SV_VulkanVertexID;
[vk::location(0)] float3 pos;
[vk::location(1)] float2 uv;
// Per Instance
[vk::location(2)] float2 world_pos;
};
struct VertexShaderOutput {
float4 pos : SV_Position;
float2 uv;
};
struct FragmentShaderOutput {
float4 color : SV_Target;
};
[shader("vertex")]
VertexShaderOutput main_vertex(VertexShaderInput input) {
var output: VertexShaderOutput;
input.pos.xy *= 0.5;
output.pos = mul(float4(input.world_pos + input.pos.xy, 0, 1), view_projection_matrix);
output.uv = input.uv;
// uint sprite_x = bitfieldExtract(input.sprite_selection, 16, 16);
// uint sprite_y = bitfieldExtract(input.sprite_selection, 0, 16);
// output.uv = (float2(sprite_x, sprite_y) + input.uv) * (1.0 / 4.0);
return output;
}
[vk::binding(0, 2)] Sampler2D<float4> texture1 : register(t0, space2);
[vk::binding(0, 3)] ConstantBuffer<float3> tint : register(b0, space3);
[shader("pixel")]
FragmentShaderOutput main_fragment(VertexShaderOutput input) {
var output: FragmentShaderOutput;
output.color = pixel_art_sample(texture1, input.uv);
output.color = float4(output.color.rgb * tint.rgb, output.color.a);
return output;
}
void GetDimensions(Sampler2D<float4> texture, out float width, out float height) {
__target_switch {
case wgsl: {
texture.__getTexture().GetDimensions(width, height);
} break;
default: {
texture.GetDimensions(width, height);
} break;
}
}
float4 pixel_art_sample(Sampler2D<float4> input_texture, float2 input_uv) {
var dimensions : float2;
GetDimensions(input_texture, dimensions.x, dimensions.y);
let texture_uv = input_uv * dimensions.xy;
let sample_uv = (floor(texture_uv) + min(fract(texture_uv) / fwidth(texture_uv), float2(1.0, 1.0)) - 0.5) / dimensions.xy;
return input_texture.Sample(sample_uv);
}
+88
View File
@@ -0,0 +1,88 @@
struct _MatrixStorage_float4x4_ColMajorstd140_0
{
@align(16) data_0 : array<vec4<f32>, i32(4)>,
};
@binding(0) @group(1) var<uniform> view_projection_matrix_0 : _MatrixStorage_float4x4_ColMajorstd140_0;
@binding(0) @group(2) var texture1_texture_0 : texture_2d<f32>;
@binding(1) @group(2) var texture1_sampler_0 : sampler;
@binding(0) @group(3) var<uniform> tint_0 : vec3<f32>;
struct VertexShaderOutput_0
{
@builtin(position) pos_0 : vec4<f32>,
@location(0) uv_0 : vec2<f32>,
};
struct vertexInput_0
{
@location(0) pos_1 : vec3<f32>,
@location(1) uv_1 : vec2<f32>,
@location(2) world_pos_0 : vec2<f32>,
};
struct VertexShaderInput_0
{
vertex_index_0 : u32,
pos_2 : vec3<f32>,
uv_2 : vec2<f32>,
world_pos_1 : vec2<f32>,
};
@vertex
fn main_vertex( _S1 : vertexInput_0, @builtin(vertex_index) vertex_index_1 : u32) -> VertexShaderOutput_0
{
var _S2 : VertexShaderInput_0;
_S2.vertex_index_0 = vertex_index_1;
_S2.pos_2 = _S1.pos_1;
_S2.uv_2 = _S1.uv_1;
_S2.world_pos_1 = _S1.world_pos_0;
var _S3 : vec2<f32> = _S2.pos_2.xy * vec2<f32>(0.5f);
_S2.pos_2.x = _S3.x;
_S2.pos_2.y = _S3.y;
var output_0 : VertexShaderOutput_0;
output_0.pos_0 = (((mat4x4<f32>(view_projection_matrix_0.data_0[i32(0)][i32(0)], view_projection_matrix_0.data_0[i32(1)][i32(0)], view_projection_matrix_0.data_0[i32(2)][i32(0)], view_projection_matrix_0.data_0[i32(3)][i32(0)], view_projection_matrix_0.data_0[i32(0)][i32(1)], view_projection_matrix_0.data_0[i32(1)][i32(1)], view_projection_matrix_0.data_0[i32(2)][i32(1)], view_projection_matrix_0.data_0[i32(3)][i32(1)], view_projection_matrix_0.data_0[i32(0)][i32(2)], view_projection_matrix_0.data_0[i32(1)][i32(2)], view_projection_matrix_0.data_0[i32(2)][i32(2)], view_projection_matrix_0.data_0[i32(3)][i32(2)], view_projection_matrix_0.data_0[i32(0)][i32(3)], view_projection_matrix_0.data_0[i32(1)][i32(3)], view_projection_matrix_0.data_0[i32(2)][i32(3)], view_projection_matrix_0.data_0[i32(3)][i32(3)])) * (vec4<f32>(_S2.world_pos_1 + _S2.pos_2.xy, 0.0f, 1.0f))));
output_0.uv_0 = _S2.uv_2;
return output_0;
}
fn GetDimensions_0( texture_texture_0 : texture_2d<f32>, texture_sampler_0 : sampler, width_0 : ptr<function, f32>, height_0 : ptr<function, f32>)
{
{var dim = textureDimensions((texture_texture_0));(((*width_0))) = f32(dim.x);(((*height_0))) = f32(dim.y);};
return;
}
fn pixel_art_sample_0( input_texture_texture_0 : texture_2d<f32>, input_texture_sampler_0 : sampler, input_uv_0 : vec2<f32>) -> vec4<f32>
{
var dimensions_0 : vec2<f32>;
var _S4 : f32 = dimensions_0[i32(0)];
var _S5 : f32 = dimensions_0[i32(1)];
GetDimensions_0(input_texture_texture_0, input_texture_sampler_0, &(_S4), &(_S5));
dimensions_0[i32(0)] = _S4;
dimensions_0[i32(1)] = _S5;
var _S6 : vec2<f32> = input_uv_0 * dimensions_0.xy;
var _S7 : vec2<f32> = (floor(_S6) + min(fract(_S6) / (fwidth((_S6))), vec2<f32>(1.0f, 1.0f)) - vec2<f32>(0.5f)) / dimensions_0.xy;
;
return (textureSample((input_texture_texture_0), (input_texture_sampler_0), (_S7)));
}
struct FragmentShaderOutput_0
{
@location(0) color_0 : vec4<f32>,
};
struct pixelInput_0
{
@location(0) uv_3 : vec2<f32>,
};
@fragment
fn main_fragment( _S8 : pixelInput_0, @builtin(position) pos_3 : vec4<f32>) -> FragmentShaderOutput_0
{
var output_1 : FragmentShaderOutput_0;
var _S9 : vec4<f32> = pixel_art_sample_0(texture1_texture_0, texture1_sampler_0, _S8.uv_3);
output_1.color_0 = vec4<f32>(_S9.xyz * tint_0.xyz, _S9.w);
return output_1;
}