diff --git a/CMakeLists.txt b/CMakeLists.txt index 2cee8f2..54f3051 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,6 +108,7 @@ endfunction() add_shader(character) add_shader(character_shadow) add_shader(world) +add_shader(tree) add_shader(grid) add_custom_target(shaders-spv DEPENDS ${SHADERS_SPV}) diff --git a/assets/maps/map.sv b/assets/maps/map.sv index 47cc6c2..3a694d4 100644 Binary files a/assets/maps/map.sv and b/assets/maps/map.sv differ diff --git a/src/main.cpp b/src/main.cpp index 23e970b..9d73706 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -9,6 +10,7 @@ #include #include #include +#include "smol-atlas.h" #include #include @@ -31,6 +33,7 @@ using namespace glm; #define TILE_SIZE (32) #define TILE_ATLAS_SIZE (512) +#define TREE_ATLAS_SIZE (256) static SDL_Window *window; static R_Texture framebuffer; @@ -39,13 +42,17 @@ static R_Texture player_texture; static R_Texture character_shadow_texture; static R_Texture tile_textures_atlas; static R_Texture tile_textures_atlas_imgui; +static R_Texture tree_textures_atlas; +static R_Texture tree_textures_atlas_imgui; static R_Buffer view_projection_matrix_buffer; static R_Buffer per_frame_buffer; static R_Buffer tint_color_buffer; static R_Buffer player_instance_buffer; +static R_Buffer trees_instance_buffer; static R_Buffer tile_uvs_buffer; +static R_Buffer tree_uvs_buffer; static i32vec2 window_size = { 1280, 720 }; @@ -87,6 +94,7 @@ static float editor_camera_distance = 30.0f; static bool show_demo_window; static bool show_tile_picker; +static bool show_tree_editor; static bool show_settings; static float character_speed = 4.0f; @@ -175,15 +183,22 @@ struct Instance { static Instance player_instance = {{ 0.0f, 0.0f }}; -struct Map { - Uint32 version; +struct Tree { + u32vec2 pos; + Uint32 kind; +}; +struct Map { i32vec2 size; Uint16 *tiles; + Uint32 num_trees; + Tree *trees; + char name[64]; R_Texture texture; + R_Buffer trees_buffer; }; static Map current_map; @@ -395,6 +410,19 @@ static TileInfo tile_infos[] = { static vec4 tile_uvs[SDL_arraysize(tile_infos)]; +struct TreeInfo { + Uint16 serialization_id; + const char *asset_path; +}; + +static TreeInfo tree_infos[] = { + { 0, "decorations/tree_standard_tile.png" }, +}; + +static vec4 tree_uvs[SDL_arraysize(tree_infos)]; + +static Sint32 selected_tree = -1; + static Sint32 selected_tile_kind = -1; static Sint32 selected_tile = -1; @@ -427,11 +455,12 @@ static R_Texture create_texture(const char *path) { return result; } -#define MAP_FILE_VERSION (2u) +#define MAP_FILE_VERSION (3u) static bool save_map(Map map) { char path[256] = ASSETS_PATH "maps/"; SDL_strlcat(path, map.name, SDL_arraysize(path)); + SDL_strlcat(path, ".sv", SDL_arraysize(path)); SDL_IOStream *file = SDL_IOFromFile(path, "wb"); if (!file) { @@ -464,6 +493,30 @@ static bool save_map(Map map) { } } + if (!SDL_WriteU32LE(file, map.num_trees)) { + log_error("Failed to write num_trees to map file."); + return false; + } + + for (int i = 0; i < map.num_trees; i++) { + Uint16 id = tree_infos[map.trees[i].kind].serialization_id; + + if (!SDL_WriteU16LE(file, id)) { + log_error("Failed to write tree kind to map file."); + return false; + } + + if (!SDL_WriteU32LE(file, map.trees[i].pos.x)) { + log_error("Failed to write tree pos.x to map file."); + return false; + } + + if (!SDL_WriteU32LE(file, map.trees[i].pos.y)) { + log_error("Failed to write tree pos.y to map file."); + return false; + } + } + if(!SDL_FlushIO(file)) { log_error("Failed to flush data to map file."); return false; @@ -476,6 +529,7 @@ static bool save_map(Map map) { static bool load_map(const char *name, Map *result) { char path[256] = ASSETS_PATH "maps/"; SDL_strlcat(path, name, SDL_arraysize(path)); + SDL_strlcat(path, ".sv", SDL_arraysize(path)); SDL_IOStream *file = SDL_IOFromFile(path, "rb"); if (!file) { @@ -485,13 +539,19 @@ static bool load_map(const char *name, Map *result) { defer(SDL_CloseIO(file)); SDL_memcpy(result->name, name, SDL_min(strlen(name), SDL_arraysize(result->name) - 1)); - if (!SDL_ReadU32LE(file, &result->version)) { + Uint32 version = 0; + if (!SDL_ReadU32LE(file, &version)) { log_error("Failed read version from map file."); return false; } - if (result->version > MAP_FILE_VERSION) { - log_error("Map file version (%u) is higher than the highest supported.", result->version); + if (!version) { + log_error("Map file version for map '%s' was 0 which is not valid.", name); + return false; + } + + if (version > 3) { + log_error("Map file version (%u) is higher than the highest supported.", version); return false; } @@ -505,10 +565,10 @@ static bool load_map(const char *name, Map *result) { return false; } - result->tiles = (Uint16*)malloc(result->size.x * result->size.y * sizeof(Uint16)); + result->tiles = (Uint16 *)SDL_malloc(result->size.x * result->size.y * sizeof(Uint16)); for (int i = 0; i < result->size.x * result->size.y; i++) { - if (result->version == 2) { + if (version >= 2) { Uint16 serialization_id = 0; if (!SDL_ReadU16LE(file, &serialization_id)) { free(result->tiles); @@ -532,11 +592,58 @@ static bool load_map(const char *name, Map *result) { } } + if (version >= 3) { + if (!SDL_ReadU32LE(file, &result->num_trees)) { + log_error("Failed to read num_trees from map file."); + free(result->tiles); + return false; + } + + result->trees = (Tree *)SDL_malloc(result->num_trees * sizeof(Tree)); + + for (int i = 0; i < result->num_trees; i++) { + Uint16 serialization_id = 0; + if (!SDL_ReadU16LE(file, &serialization_id)) { + free(result->tiles); + free(result->trees); + return false; + } + + Uint16 info_index = 0; + for (int i = 0; i < SDL_arraysize(tree_infos); i++) { + if (tree_infos[i].serialization_id == serialization_id) { + info_index = i; + break; + } + } + + result->trees[i].kind = info_index; + + if (!SDL_ReadU32LE(file, &result->trees[i].pos.x)) { + free(result->tiles); + free(result->trees); + log_error("Failed to read tree pos.x from map file."); + return false; + } + + if (!SDL_ReadU32LE(file, &result->trees[i].pos.y)) { + free(result->tiles); + free(result->trees); + log_error("Failed to read tree pos.y from map file."); + return false; + } + } + } + char buffer_name[256] = "Map "; SDL_strlcat(buffer_name, result->name, SDL_arraysize(buffer_name)); result->texture = renderer_texture_create(R_TEXTURE_FORMAT_R16_UINT, R_TEXTURE_USAGE_STORAGE, false, result->size.x, result->size.y, result->tiles, "map_texture"); + if (result->num_trees) { + result->trees_buffer = renderer_buffer_create(R_BUFFER_USAGE_VERTEX, result->num_trees * sizeof(*result->trees), result->trees, "trees_buffer"); + } + SDL_Log("Loaded map file."); return true; } @@ -639,6 +746,20 @@ static bool SelectableTile(const char *label, bool selected, Uint32 tile_index, return pressed; } +static bool SelectableImage(const char *label, R_Texture texture, ImVec2 image_size, vec2 uv_min, vec2 uv_max, bool selected) { + const ImGuiContext *context = ImGui::GetCurrentContext(); + const ImVec2 padding = context->Style.FramePadding; + + bool pressed = ImGui::Selectable(label, selected, 0, ImVec2(image_size.x, image_size.y) + padding * 2.0f); + + ImVec2 min = ImGui::GetItemRectMin(); + ImVec2 max = ImGui::GetItemRectMax(); + + context->CurrentWindow->DrawList->AddImageQuad((ImTextureID)ImGui_ImplRenderer_GetTextureID(texture), min + padding, ImVec2(max.x - padding.x, min.y + padding.y), max - padding, ImVec2(min.x + padding.x, max.y - padding.y), ImVec2(uv_min.x, uv_min.y), ImVec2(uv_max.x, uv_min.y), ImVec2(uv_max.x, uv_max.y), ImVec2(uv_min.x, uv_max.y)); + + return pressed; +} + static ImVec4 linear_to_sRGB(ImVec4 linear) { float red = linear.x <= 0.0031308f ? 12.92f * linear.x : 1.055f * powf(linear.x, 1.0f / 2.4f) - 0.055; float green = linear.y <= 0.0031308f ? 12.92f * linear.y : 1.055f * powf(linear.y, 1.0f / 2.4f) - 0.055; @@ -776,6 +897,68 @@ static bool recreate_tile_textures() { return true; } +static bool recreate_tree_textures() { + smol_atlas_t *tree_atlas = sma_atlas_create(TREE_ATLAS_SIZE, TREE_ATLAS_SIZE); + smol_atlas_item_t **tree_atlas_items = (smol_atlas_item_t **)SDL_calloc(SDL_arraysize(tree_infos), sizeof(smol_atlas_item_t *)); + + defer(sma_atlas_destroy(tree_atlas)); + defer(SDL_free(tree_atlas_items)); + + for (Uint32 i = 0; i < SDL_arraysize(tree_infos); i++) { + char path[256] = ASSETS_PATH; + SDL_strlcat(path, tree_infos[i].asset_path, SDL_arraysize(path)); + + int width = 0, height = 0; + if (!stbi_info(path, &width, &height, NULL)) + return false; + + tree_atlas_items[i] = sma_item_add(tree_atlas, width, height); + if (!tree_atlas_items[i]) + return false; + } + + tree_textures_atlas = renderer_texture_create(R_TEXTURE_FORMAT_RGBA8_UNORM_SRGB, R_TEXTURE_USAGE_SAMPLED, false, TREE_ATLAS_SIZE, TREE_ATLAS_SIZE, NULL, "tree_atlas_texture"); + if (!tree_textures_atlas) { + log_error("Failed to create texture."); + return false; + } + + tree_textures_atlas_imgui = renderer_texture_create(R_TEXTURE_FORMAT_RGBA8_UNORM, R_TEXTURE_USAGE_SAMPLED, false, TREE_ATLAS_SIZE, TREE_ATLAS_SIZE, NULL, "tree_atlas_texture imgui"); + if (!tree_textures_atlas_imgui) { + log_error("Failed to create texture."); + return false; + } + + for (Uint32 i = 0; i < SDL_arraysize(tree_infos); i++) { + char path[256] = ASSETS_PATH; + SDL_strlcat(path, tree_infos[i].asset_path, SDL_arraysize(path)); + + int width = 0, height = 0; + stbi_uc *data = stbi_load(path, &width, &height, NULL, 4); + if (!data) { + log_error("Failed to load texture (\"%s\"). Exiting.", path); + renderer_texture_destroy(tree_textures_atlas); + tree_textures_atlas = NULL; + renderer_texture_destroy(tree_textures_atlas_imgui); + tree_textures_atlas_imgui = NULL; + return false; + } + + tree_uvs[i].x = sma_item_x(tree_atlas_items[i]); + tree_uvs[i].y = sma_item_y(tree_atlas_items[i]); + tree_uvs[i].z = sma_item_x(tree_atlas_items[i]) + sma_item_width (tree_atlas_items[i]); + tree_uvs[i].w = sma_item_y(tree_atlas_items[i]) + sma_item_height(tree_atlas_items[i]); + + renderer_texture_update(tree_textures_atlas, sma_item_x(tree_atlas_items[i]), sma_item_y(tree_atlas_items[i]), sma_item_width(tree_atlas_items[i]), sma_item_height(tree_atlas_items[i]), data, sma_item_width(tree_atlas_items[i]) * 4); + renderer_texture_update(tree_textures_atlas_imgui, sma_item_x(tree_atlas_items[i]), sma_item_y(tree_atlas_items[i]), sma_item_width(tree_atlas_items[i]), sma_item_height(tree_atlas_items[i]), data, sma_item_width(tree_atlas_items[i]) * 4); + stbi_image_free(data); + } + + tree_uvs_buffer = renderer_buffer_create(R_BUFFER_USAGE_STORAGE, sizeof(tree_uvs), tree_uvs, "tree_uvs_buffer"); + + return true; +} + static int real_mod(int a, int b) { int result = a % b; return result >= 0 ? result : result + b; @@ -854,6 +1037,13 @@ static i32vec2 grid_tile_pos_from_floor_intersection(vec2 floor_intersection) { }; } +static i32vec2 grid_entity_pos_from_floor_intersection(vec2 floor_intersection) { + return { + (Sint32)SDL_floorf(floor_intersection.x + 0.5f), + (Sint32)SDL_floorf(floor_intersection.y + 0.5f), + }; +} + static bool init_resources() { view_projection_matrix_buffer = renderer_buffer_create(R_BUFFER_USAGE_UNIFORM, sizeof(mat4x4), NULL, "view_projection_matrix_buffer"); if (!view_projection_matrix_buffer) { @@ -896,6 +1086,11 @@ static bool init_resources() { return false; } + if (!recreate_tree_textures()) { + log_error("Failed to create tree textures."); + return false; + } + return true; } @@ -953,7 +1148,8 @@ static void process_event_editor(SDL_Event event) { return; vec2 floor_intersection = get_floor_intersection_of_mouse(vec2(event.button.x, event.button.y)); - i32vec2 tile_pos = grid_tile_pos_from_floor_intersection(floor_intersection); + i32vec2 tile_pos = grid_tile_pos_from_floor_intersection(floor_intersection); + i32vec2 entity_pos = grid_entity_pos_from_floor_intersection(floor_intersection); drag_start_pos = floor_intersection; @@ -962,6 +1158,48 @@ static void process_event_editor(SDL_Event event) { } if (event.button.button == SDL_BUTTON_LEFT) { + if (selected_tree != -1 && 0 <= tile_pos.x && tile_pos.x < current_map.size.x -1 && 0 <= tile_pos.y && tile_pos.y < current_map.size.y - 1) { + Sint32 found_tree = -1; + for (Sint32 i = 0; i < current_map.num_trees; i++) { + if ((current_map.trees[i].pos.x == entity_pos.x) && (current_map.trees[i].pos.y == entity_pos.y)) { + found_tree = i; + break; + } + } + + if (found_tree != -1) { + Tree tmp = current_map.trees[found_tree]; + SDL_memmove(¤t_map.trees[found_tree], ¤t_map.trees[found_tree + 1], (current_map.num_trees - (found_tree + 1)) * sizeof(*current_map.trees)); + current_map.trees[current_map.num_trees - 1] = tmp; + } else { + current_map.trees = (Tree *)SDL_realloc(current_map.trees, (current_map.num_trees + 1) * sizeof(*current_map.trees)); + current_map.trees[current_map.num_trees] = { + .pos = entity_pos, + .kind = (Uint32)selected_tree, + }; + + current_map.num_trees++; + } + + std::stable_sort(¤t_map.trees[0], ¤t_map.trees[current_map.num_trees], [](const Tree &a, const Tree &b){ return a.pos.y > b.pos.y; }); + + if (current_map.trees_buffer) renderer_buffer_destroy(current_map.trees_buffer); + current_map.trees_buffer = renderer_buffer_create(R_BUFFER_USAGE_VERTEX, current_map.num_trees * sizeof(*current_map.trees), current_map.trees, "trees_buffer"); + } + + if (show_tree_editor && selected_tree == -1) { + for (Sint32 i = 0; i < current_map.num_trees; i++) { + if ((current_map.trees[i].pos.x == entity_pos.x) && (current_map.trees[i].pos.y == entity_pos.y)) { + SDL_memmove(¤t_map.trees[i], ¤t_map.trees[i + 1], (current_map.num_trees - (i + 1)) * sizeof(*current_map.trees)); + current_map.num_trees--; + break; + } + } + + if (current_map.trees_buffer) renderer_buffer_destroy(current_map.trees_buffer); + if (current_map.num_trees) current_map.trees_buffer = renderer_buffer_create(R_BUFFER_USAGE_VERTEX, current_map.num_trees * sizeof(*current_map.trees), current_map.trees, "trees_buffer"); + } + if (selected_tile_kind != -1) { change_map_tile(tile_pos.x, tile_pos.y, (TileKind)selected_tile_kind); @@ -1075,6 +1313,7 @@ static void update_state_editor() { if (ImGui::BeginMenu("File")) { if (ImGui::MenuItem("Reload")) { recreate_tile_textures(); + recreate_tree_textures(); } if (ImGui::MenuItem("Save")) { @@ -1095,6 +1334,7 @@ static void update_state_editor() { if (ImGui::BeginMenu("Edit")) { ImGui::MenuItem("Tile Picker", NULL, &show_tile_picker); + ImGui::MenuItem("Trees", NULL, &show_tree_editor); ImGui::EndMenu(); } @@ -1109,6 +1349,7 @@ static void update_state_editor() { ImGuiID left_dock = ImGui::DockBuilderSplitNode(main_viewport_dock, ImGuiDir_Left, 0.2f, NULL, NULL); ImGui::DockBuilderDockWindow("Tile Picker", left_dock); + ImGui::DockBuilderDockWindow("Trees", left_dock); ImGui::DockBuilderFinish(main_viewport_dock); } ImGui::DockSpaceOverViewport(main_viewport_dock, ImGui::GetMainViewport(), ImGuiDockNodeFlags_AutoHideTabBar | ImGuiDockNodeFlags_PassthruCentralNode | ImGuiDockNodeFlags_NoDockingOverCentralNode); @@ -1227,6 +1468,29 @@ static void update_state_editor() { selected_tile_kind = -1; } + if (show_tree_editor) { + if (ImGui::Begin("Trees", &show_tree_editor, ImGuiWindowFlags_NoFocusOnAppearing)) { + ImGuiStyle &style = ImGui::GetStyle(); + if (SelectableImage("##tree", tree_textures_atlas_imgui, ImVec2(64, 64), vec2(0, 0), vec2(0, 0), selected_tree == -1)) + selected_tree = -1; + + for (int i = 0; i < SDL_arraysize(tree_infos); i++) { + ImGui::PushID(i); + + if (i != 0) + SameLineOrWrap(ImVec2(64, 1)); + + if (SelectableImage("##tree", tree_textures_atlas_imgui, ImVec2(tree_uvs[i].z, tree_uvs[i].w), tree_uvs[i].xy() / (float)TREE_ATLAS_SIZE, tree_uvs[i].zw() / (float)TREE_ATLAS_SIZE, selected_tree == i)) + selected_tree = i; + + ImGui::PopID(); + } + } + ImGui::End(); + } else { + selected_tree = -1; + } + ImGui::SetNextWindowPos({ viewport->WorkPos.x + viewport->WorkSize.x - 10.0f, viewport->WorkPos.y + 10.0f }, ImGuiCond_Always, { 1.0f, 0.0f }); if (ImGui::Begin("Overlay", NULL, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) { ImGui::Checkbox("Grid", &show_grid); @@ -1331,6 +1595,24 @@ static void render_editor() { }); } + if (current_map.num_trees) { + ZoneScopedN("Draw Trees"); + renderer_frame_set_shader(R_SHADER_TREE); + renderer_frame_draw(NULL, NULL, current_map.trees_buffer, 6, current_map.num_trees, &(R_Draw_Resources){ + .vertex_uniform_buffers = (R_Buffer[]){ view_projection_matrix_buffer, per_frame_buffer }, + + .num_vertex_uniform_buffers = 2, + + .fragment_sampled_textures = (R_Texture[]){ tree_textures_atlas }, + .fragment_storage_buffers = (R_Buffer[]){ tree_uvs_buffer }, + .fragment_uniform_buffers = (R_Buffer[]){ tint_color_buffer }, + + .num_fragment_sampled_textures = 1, + .num_fragment_storage_buffers = 1, + .num_fragment_uniform_buffers = 1, + }); + } + if (show_grid) { ZoneScopedN("Draw Grid"); @@ -1530,6 +1812,24 @@ static void render_game() { }); } + if (current_map.num_trees) { + ZoneScopedN("Draw Trees"); + renderer_frame_set_shader(R_SHADER_TREE); + renderer_frame_draw(NULL, NULL, current_map.trees_buffer, 6, current_map.num_trees, &(R_Draw_Resources){ + .vertex_uniform_buffers = (R_Buffer[]){ view_projection_matrix_buffer, per_frame_buffer }, + + .num_vertex_uniform_buffers = 2, + + .fragment_sampled_textures = (R_Texture[]){ tree_textures_atlas }, + .fragment_storage_buffers = (R_Buffer[]){ tree_uvs_buffer }, + .fragment_uniform_buffers = (R_Buffer[]){ tint_color_buffer }, + + .num_fragment_sampled_textures = 1, + .num_fragment_storage_buffers = 1, + .num_fragment_uniform_buffers = 1, + }); + } + { ZoneScopedN("Draw Player"); renderer_frame_set_shader(R_SHADER_CHARACTER_SHADOW); @@ -1748,7 +2048,7 @@ int main(int argc, char **argv) { return 1; } - if (!load_map("map.sv", ¤t_map)) { + if (!load_map("map", ¤t_map)) { log_error("Failed to load initial map. Exiting."); return 1; } diff --git a/src/renderer.h b/src/renderer.h index c355242..fff0e64 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -11,6 +11,7 @@ typedef enum : Uint32 { R_SHADER_CHARACTER, R_SHADER_CHARACTER_SHADOW, R_SHADER_WORLD, + R_SHADER_TREE, R_SHADER_GRID, R_SHADER_COUNT, } R_Shader; diff --git a/src/renderer_sdlgpu.c b/src/renderer_sdlgpu.c index 540d4e5..9c4de41 100644 --- a/src/renderer_sdlgpu.c +++ b/src/renderer_sdlgpu.c @@ -444,7 +444,7 @@ bool renderer_init(SDL_Window *window_) { }); SDL_PropertiesID properties = SDL_CreateProperties(); - SDL_SetStringProperty(properties, SDL_PROP_GPU_BUFFER_CREATE_NAME_STRING, "R_SHADER basic"); + SDL_SetStringProperty(properties, SDL_PROP_GPU_BUFFER_CREATE_NAME_STRING, "R_SHADER character"); shaders[R_SHADER_CHARACTER] = SDL_CreateGPUGraphicsPipeline(device, &(SDL_GPUGraphicsPipelineCreateInfo){ .vertex_shader = vertex_shader, @@ -582,7 +582,7 @@ bool renderer_init(SDL_Window *window_) { }); SDL_PropertiesID properties = SDL_CreateProperties(); - SDL_SetStringProperty(properties, SDL_PROP_GPU_BUFFER_CREATE_NAME_STRING, "R_SHADER basic"); + SDL_SetStringProperty(properties, SDL_PROP_GPU_BUFFER_CREATE_NAME_STRING, "R_SHADER character_shadow"); shaders[R_SHADER_CHARACTER_SHADOW] = SDL_CreateGPUGraphicsPipeline(device, &(SDL_GPUGraphicsPipelineCreateInfo){ .vertex_shader = vertex_shader, @@ -726,12 +726,129 @@ bool renderer_init(SDL_Window *window_) { .vertex_shader = vertex_shader, .fragment_shader = fragment_shader, + .vertex_input_state = { + .vertex_buffer_descriptions = (SDL_GPUVertexBufferDescription[]) {}, + .num_vertex_buffers = 0, + + .vertex_attributes = (SDL_GPUVertexAttribute[]) {}, + .num_vertex_attributes = 0, + }, + + .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); + } + + { // Tree + const Uint8 shader_source[] = { + #embed "shaders/spirv/tree.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 = 2, + }); + + 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 = 1, + .num_uniform_buffers = 1, + }); + + SDL_PropertiesID properties = SDL_CreateProperties(); + SDL_SetStringProperty(properties, SDL_PROP_GPU_BUFFER_CREATE_NAME_STRING, "R_SHADER tree"); + + shaders[R_SHADER_TREE] = 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, + .pitch = 12, + .input_rate = SDL_GPU_VERTEXINPUTRATE_INSTANCE, .instance_step_rate = 0, }, @@ -742,11 +859,17 @@ bool renderer_init(SDL_Window *window_) { { .location = 0, .buffer_slot = 0, - .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3, + .format = SDL_GPU_VERTEXELEMENTFORMAT_UINT2, .offset = 0, }, + { + .location = 1, + .buffer_slot = 0, + .format = SDL_GPU_VERTEXELEMENTFORMAT_UINT, + .offset = 8, + }, }, - .num_vertex_attributes = 1, + .num_vertex_attributes = 2, }, .primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST, diff --git a/src/renderer_wgpu.c b/src/renderer_wgpu.c index cc63124..c754276 100644 --- a/src/renderer_wgpu.c +++ b/src/renderer_wgpu.c @@ -971,6 +971,110 @@ bool renderer_init(SDL_Window *window_) { wgpuShaderModuleRelease(shader); } + { // Tree + const char shader_source[] = { + #embed "shaders/wgsl/tree.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 = "tree shader", .length = WGPU_STRLEN } + }); + + shaders[R_SHADER_TREE] = wgpuDeviceCreateRenderPipeline(device, &(WGPURenderPipelineDescriptor){ + .label = { .data = "tree render_pipeline", .length = WGPU_STRLEN }, + + .layout = wgpuDeviceCreatePipelineLayout(device, &(WGPUPipelineLayoutDescriptor){ + .label = { .data = "tree pipeline_layout", .length = WGPU_STRLEN }, + .bindGroupLayoutCount = 4, + .bindGroupLayouts = (WGPUBindGroupLayout[]){ + wgpuDeviceCreateBindGroupLayout(device, &(WGPUBindGroupLayoutDescriptor){ + .entryCount = 0, + .entries = (WGPUBindGroupLayoutEntry[]){}, + }), + wgpuDeviceCreateBindGroupLayout(device, &(WGPUBindGroupLayoutDescriptor){ + .entryCount = 2, + .entries = (WGPUBindGroupLayoutEntry[]){ + { .binding = 0, .visibility = WGPUShaderStage_Vertex, .buffer = { .type = WGPUBufferBindingType_Uniform } }, + { .binding = 1, .visibility = WGPUShaderStage_Vertex, .buffer = { .type = WGPUBufferBindingType_Uniform } }, + }, + }), + wgpuDeviceCreateBindGroupLayout(device, &(WGPUBindGroupLayoutDescriptor){ + .entryCount = 3, + .entries = (WGPUBindGroupLayoutEntry[]){ + { .binding = 0, .visibility = WGPUShaderStage_Fragment, .texture = { .sampleType = WGPUTextureSampleType_Float, .viewDimension = WGPUTextureViewDimension_2D } }, + { .binding = 1, .visibility = WGPUShaderStage_Fragment, .sampler = { .type = WGPUSamplerBindingType_Filtering } }, + { .binding = 2, .visibility = WGPUShaderStage_Fragment, .buffer = { .type = WGPUBufferBindingType_ReadOnlyStorage } }, + }, + }), + 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 = 1, + .buffers = (WGPUVertexBufferLayout[]){ + { + .stepMode = WGPUVertexStepMode_Instance, + .arrayStride = 12, + .attributeCount = 2, + .attributes = (WGPUVertexAttribute[]){ + { + .format = WGPUVertexFormat_Uint32x2, + .offset = 0, + .shaderLocation = 0, + }, + { + .format = WGPUVertexFormat_Uint32, + .offset = 8, + .shaderLocation = 1, + }, + }, + }, + }, + }, + + .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); + } + { // Grid const char shader_source[] = { #embed "shaders/wgsl/grid.wgsl" diff --git a/src/shaders/spirv/tree.spv b/src/shaders/spirv/tree.spv new file mode 100644 index 0000000..5e39881 Binary files /dev/null and b/src/shaders/spirv/tree.spv differ diff --git a/src/shaders/src/tree.slang b/src/shaders/src/tree.slang new file mode 100644 index 0000000..4b2297b --- /dev/null +++ b/src/shaders/src/tree.slang @@ -0,0 +1,86 @@ +#language 2026 + +#include "shared.slang" +#include "wgsl_workaround.slang" + +struct VertexShaderInput { + uint32_t vertex_index : SV_VulkanVertexID; + uint32_t instance_index : SV_VulkanInstanceID; + + // Per Instance + [vk::location(0)] uint32_t2 pos; + [vk::location(1)] uint32_t kind; +}; + +struct VertexShaderOutput { + float4 pos : SV_Position; + + float2 uv; + uint32_t kind; +}; + +struct FragmentShaderOutput { + float4 color : SV_Target; +}; + +static const float2 offset = float2(0, 0.5); + +[shader("vertex")] +VertexShaderOutput main_vertex(VertexShaderInput input) { + var output: VertexShaderOutput; + + var vertex_pos = float2(0, 0); + var vertex_uv = float2(0, 0); + + switch (input.vertex_index) { + case 0: { vertex_pos = float2(-1.0, 1.0); vertex_uv = float2(0, 0); } break; + case 1: { vertex_pos = float2(-1.0, -1.0); vertex_uv = float2(0, 1); } break; + case 2: { vertex_pos = float2( 1.0, -1.0); vertex_uv = float2(1, 1); } break; + case 3: { vertex_pos = float2(-1.0, 1.0); vertex_uv = float2(0, 0); } break; + case 4: { vertex_pos = float2( 1.0, -1.0); vertex_uv = float2(1, 1); } break; + case 5: { vertex_pos = float2( 1.0, 1.0); vertex_uv = float2(1, 0); } break; + default: {} + } + + vertex_pos += offset; + + output.pos = mul(float4(input.pos + vertex_pos, 0, 1), view_projection_matrix); + output.uv = vertex_uv; + output.kind = input.kind; + + return output; +} + +struct Block2 { + CombinedTextureSampler tree_atlas; + StructuredBuffer tree_uvs; +}; + +struct Block3 { + ConstantBuffer tint; +}; + +[vk::binding(0, 2)] ParameterBlock block2; +[vk::binding(0, 3)]ParameterBlock block3; + +[shader("pixel")] +FragmentShaderOutput main_fragment(VertexShaderOutput input) { + var output: FragmentShaderOutput; + + output.color = pixel_art_sample(block2.tree_atlas, input.uv, block2.tree_uvs[input.kind]); + output.color = float4(output.color.rgb * block3.tint.rgb, output.color.a); + + return output; +} + +float4 pixel_art_sample(CombinedTextureSampler input_texture, float2 input_uv, float4 tree_uv) { + var dimensions : float2; + input_texture.getTexture().GetDimensions(dimensions.x, dimensions.y); + + let texture_uv = lerp(tree_uv.xy, tree_uv.zw, input_uv); + let sample_uv = (floor(texture_uv) + saturate(fract(texture_uv) / fwidth(texture_uv)) - 0.5) / dimensions; + + let uv = clamp(sample_uv, (tree_uv.xy + 0.5) / dimensions, (tree_uv.zw - 0.5) / dimensions); + + return input_texture.Sample(uv); +} diff --git a/src/shaders/wgsl/tree.wgsl b/src/shaders/wgsl/tree.wgsl new file mode 100644 index 0000000..b9c17d9 --- /dev/null +++ b/src/shaders/wgsl/tree.wgsl @@ -0,0 +1,123 @@ +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 block2_tree_atlas_texture_0 : texture_2d; + +@binding(1) @group(2) var block2_tree_atlas_sampler_0 : sampler; + +@binding(2) @group(2) var block2_tree_uvs_0 : array>; + +@binding(0) @group(3) var block3_tint_0 : vec3; +struct VertexShaderOutput_0 +{ + @builtin(position) pos_0 : vec4, + @location(0) uv_0 : vec2, + @location(1) kind_0 : u32, +}; + +struct vertexInput_0 +{ + @location(0) pos_1 : vec2, + @location(1) kind_1 : u32, +}; + +@vertex +fn main_vertex( _S1 : vertexInput_0, @builtin(vertex_index) vertex_index_0 : u32, @builtin(instance_index) instance_index_0 : u32) -> VertexShaderOutput_0 +{ + var output_0 : VertexShaderOutput_0; + const _S2 : vec2 = vec2(0.0f, 0.0f); + var vertex_uv_0 : vec2; + var vertex_pos_0 : vec2; + switch(vertex_index_0) + { + case u32(0): + { + const _S3 : vec2 = vec2(-1.0f, 1.0f); + vertex_uv_0 = _S2; + vertex_pos_0 = _S3; + } + case u32(1): + { + const _S4 : vec2 = vec2(-1.0f, -1.0f); + vertex_uv_0 = vec2(0.0f, 1.0f); + vertex_pos_0 = _S4; + } + case u32(2): + { + const _S5 : vec2 = vec2(1.0f, -1.0f); + vertex_uv_0 = vec2(1.0f, 1.0f); + vertex_pos_0 = _S5; + } + case u32(3): + { + const _S6 : vec2 = vec2(-1.0f, 1.0f); + vertex_uv_0 = _S2; + vertex_pos_0 = _S6; + } + case u32(4): + { + const _S7 : vec2 = vec2(1.0f, -1.0f); + vertex_uv_0 = vec2(1.0f, 1.0f); + vertex_pos_0 = _S7; + } + case u32(5): + { + const _S8 : vec2 = vec2(1.0f, 1.0f); + vertex_uv_0 = vec2(1.0f, 0.0f); + vertex_pos_0 = _S8; + } + default : + { + vertex_uv_0 = _S2; + vertex_pos_0 = _S2; + } + } + 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(vec2(_S1.pos_1) + (vertex_pos_0 + vec2(0.0f, 0.5f)), 0.0f, 1.0f)))); + output_0.uv_0 = vertex_uv_0; + output_0.kind_0 = _S1.kind_1; + return output_0; +} + +fn CombinedTextureSampler_Sample_0( this_texture_0 : texture_2d, this_sampler_0 : sampler, location_0 : vec2) -> vec4 +{ + return (textureSample((this_texture_0), (this_sampler_0), (location_0))); +} + +fn pixel_art_sample_0( input_texture_texture_0 : texture_2d, input_texture_sampler_0 : sampler, input_uv_0 : vec2, tree_uv_0 : vec4) -> vec4 +{ + var dimensions_0 : vec2; + var _S9 : f32 = dimensions_0[i32(0)]; + var _S10 : f32 = dimensions_0[i32(1)]; + {var dim = textureDimensions((input_texture_texture_0));((_S9)) = f32(dim.x);((_S10)) = f32(dim.y);}; + dimensions_0[i32(0)] = _S9; + dimensions_0[i32(1)] = _S10; + var _S11 : vec2 = tree_uv_0.xy; + var _S12 : vec2 = tree_uv_0.zw; + var _S13 : vec2 = mix(_S11, _S12, input_uv_0); + var _S14 : vec2 = vec2(0.5f); + return CombinedTextureSampler_Sample_0(input_texture_texture_0, input_texture_sampler_0, clamp((floor(_S13) + saturate(fract(_S13) / (fwidth((_S13)))) - _S14) / dimensions_0, (_S11 + _S14) / dimensions_0, (_S12 - _S14) / dimensions_0)); +} + +struct FragmentShaderOutput_0 +{ + @location(0) color_0 : vec4, +}; + +struct pixelInput_0 +{ + @location(0) uv_1 : vec2, + @location(1) kind_2 : u32, +}; + +@fragment +fn main_fragment( _S15 : pixelInput_0, @builtin(position) pos_2 : vec4) -> FragmentShaderOutput_0 +{ + var output_1 : FragmentShaderOutput_0; + var _S16 : vec4 = pixel_art_sample_0(block2_tree_atlas_texture_0, block2_tree_atlas_sampler_0, _S15.uv_1, block2_tree_uvs_0[_S15.kind_2]); + output_1.color_0 = vec4(_S16.xyz * block3_tint_0.xyz, _S16.w); + return output_1; +} +