diff --git a/CMakeLists.txt b/CMakeLists.txt index 236c4c6..2cee8f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/src/main.cpp b/src/main.cpp index 7943cba..8d8ac2c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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 }, diff --git a/src/renderer.h b/src/renderer.h index 8524782..c355242 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -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, diff --git a/src/renderer_sdlgpu.c b/src/renderer_sdlgpu.c index 9971dc6..09baace 100644 --- a/src/renderer_sdlgpu.c +++ b/src/renderer_sdlgpu.c @@ -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, diff --git a/src/renderer_wgpu.c b/src/renderer_wgpu.c index 93c4511..4b4ef6d 100644 --- a/src/renderer_wgpu.c +++ b/src/renderer_wgpu.c @@ -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){ diff --git a/src/shaders/spirv/basic.spv b/src/shaders/spirv/character.spv similarity index 100% rename from src/shaders/spirv/basic.spv rename to src/shaders/spirv/character.spv diff --git a/src/shaders/spirv/character_shadow.spv b/src/shaders/spirv/character_shadow.spv new file mode 100644 index 0000000..3238c17 Binary files /dev/null and b/src/shaders/spirv/character_shadow.spv differ diff --git a/src/shaders/src/basic.slang b/src/shaders/src/character.slang similarity index 100% rename from src/shaders/src/basic.slang rename to src/shaders/src/character.slang diff --git a/src/shaders/src/character_shadow.slang b/src/shaders/src/character_shadow.slang new file mode 100644 index 0000000..e808c5c --- /dev/null +++ b/src/shaders/src/character_shadow.slang @@ -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 texture1 : register(t0, space2); + +[vk::binding(0, 3)] ConstantBuffer 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 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 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); +} diff --git a/src/shaders/wgsl/basic.wgsl b/src/shaders/wgsl/character.wgsl similarity index 100% rename from src/shaders/wgsl/basic.wgsl rename to src/shaders/wgsl/character.wgsl diff --git a/src/shaders/wgsl/character_shadow.wgsl b/src/shaders/wgsl/character_shadow.wgsl new file mode 100644 index 0000000..8f27fe6 --- /dev/null +++ b/src/shaders/wgsl/character_shadow.wgsl @@ -0,0 +1,88 @@ +struct _MatrixStorage_float4x4_ColMajorstd140_0 +{ + @align(16) data_0 : array, i32(4)>, +}; + +@binding(0) @group(1) var view_projection_matrix_0 : _MatrixStorage_float4x4_ColMajorstd140_0; +@binding(0) @group(2) var texture1_texture_0 : texture_2d; + +@binding(1) @group(2) var texture1_sampler_0 : sampler; + +@binding(0) @group(3) var tint_0 : vec3; +struct VertexShaderOutput_0 +{ + @builtin(position) pos_0 : vec4, + @location(0) uv_0 : vec2, +}; + +struct vertexInput_0 +{ + @location(0) pos_1 : vec3, + @location(1) uv_1 : vec2, + @location(2) world_pos_0 : vec2, +}; + +struct VertexShaderInput_0 +{ + vertex_index_0 : u32, + pos_2 : vec3, + uv_2 : vec2, + world_pos_1 : vec2, +}; + +@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 = _S2.pos_2.xy * vec2(0.5f); + _S2.pos_2.x = _S3.x; + _S2.pos_2.y = _S3.y; + var output_0 : VertexShaderOutput_0; + output_0.pos_0 = (((mat4x4(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(_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, texture_sampler_0 : sampler, width_0 : ptr, height_0 : ptr) +{ + {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, input_texture_sampler_0 : sampler, input_uv_0 : vec2) -> vec4 +{ + var dimensions_0 : vec2; + 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 = input_uv_0 * dimensions_0.xy; + var _S7 : vec2 = (floor(_S6) + min(fract(_S6) / (fwidth((_S6))), vec2(1.0f, 1.0f)) - vec2(0.5f)) / dimensions_0.xy; + ; + return (textureSample((input_texture_texture_0), (input_texture_sampler_0), (_S7))); +} + +struct FragmentShaderOutput_0 +{ + @location(0) color_0 : vec4, +}; + +struct pixelInput_0 +{ + @location(0) uv_3 : vec2, +}; + +@fragment +fn main_fragment( _S8 : pixelInput_0, @builtin(position) pos_3 : vec4) -> FragmentShaderOutput_0 +{ + var output_1 : FragmentShaderOutput_0; + var _S9 : vec4 = pixel_art_sample_0(texture1_texture_0, texture1_sampler_0, _S8.uv_3); + output_1.color_0 = vec4(_S9.xyz * tint_0.xyz, _S9.w); + return output_1; +} +