From b549728a241c40dfa7584a2d899db8bca286137d Mon Sep 17 00:00:00 2001 From: Sven Balzer <4653051+Kyuusokuna@users.noreply.github.com> Date: Thu, 2 Apr 2026 17:52:28 +0200 Subject: [PATCH] change world tiles texture from texture_2d_array to an atlas texture_2d --- src/main.cpp | 99 +++++++++++++++++++++++++----------------- src/shaders/world.wgsl | 20 ++++++--- 2 files changed, 72 insertions(+), 47 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 6e7fdce..cfa6337 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,7 +24,9 @@ using namespace M; #define ASSETS_PATH "./assets/" #define NEAR_PLANE (0.01f) -#define TILE_SIZE (32) + +#define TILE_SIZE (32) +#define TILE_ATLAS_SIZE (256) static SDL_Window *window; static bool wgpu_init_done; @@ -47,9 +49,9 @@ static WGPUBindGroup basic_bind_group; static WGPUTexture player_texture; static WGPUTextureView player_texture_view; -static WGPUTexture tile_textures_array; -static WGPUTextureView tile_textures_array_view; -static WGPUTextureView *tile_textures_array_view_individual; +static WGPUTexture tile_textures_atlas; +static WGPUTextureView tile_textures_atlas_view; +static WGPUTextureView tile_textures_atlas_view_unorm; static WGPUBuffer view_projection_matrix_buffer; static WGPUBuffer per_frame_buffer; @@ -60,7 +62,7 @@ static WGPUBuffer index_buffer; static WGPUBuffer grid_vertex_buffer; static WGPUBuffer grid_index_buffer; static WGPUBuffer player_instance_buffer; -static WGPUBuffer tile_infos_buffer; +static WGPUBuffer tile_uvs_buffer; static WGPUSurfaceConfiguration surface_configuration; @@ -261,6 +263,8 @@ static TileInfo tile_infos[] = { { 0x0423, "tiles/grass_ground_two_corner.png", TILE_CORNER_INFO(TILEKIND_GRASS, TILEKIND_GROUND, TILEKIND_GRASS, TILEKIND_GROUND ) }, }; +static V4 tile_uvs[SDL_arraysize(tile_infos)]; + static Sint32 selected_tile_kind = -1; static Sint32 selected_tile = -1; @@ -566,7 +570,7 @@ static void blit(char *dst, Sint32 dst_pitch, Sint32 dst_x, Sint32 dst_y, char * memmove(&dst[((dst_y + y) * dst_pitch + dst_x) * components], &src[y * src_pitch * components], width * components); } -static bool SelectableTile(const char *label, bool selected, Uint32 tile_index, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), Uint8 orientation = 0) { +static bool SelectableTile(const char *label, bool selected, Uint32 tile_index, const ImVec2& image_size, Uint8 orientation = 0) { const ImGuiContext *context = ImGui::GetCurrentContext(); const ImVec2 padding = context->Style.FramePadding; @@ -575,11 +579,13 @@ static bool SelectableTile(const char *label, bool selected, Uint32 tile_index, ImVec2 min = ImGui::GetItemRectMin(); ImVec2 max = ImGui::GetItemRectMax(); + V4 uv = tile_uvs[tile_index] / TILE_ATLAS_SIZE; + switch (orientation) { - case 0: context->CurrentWindow->DrawList->AddImageQuad((ImTextureID)tile_textures_array_view_individual[tile_index], min + padding, ImVec2(max.x - padding.x, min.y + padding.y), max - padding, ImVec2(min.x + padding.x, max.y - padding.y), ImVec2(uv0.x, uv0.y), ImVec2(uv1.x, uv0.y), ImVec2(uv1.x, uv1.y), ImVec2(uv0.x, uv1.y)); break; - case 1: context->CurrentWindow->DrawList->AddImageQuad((ImTextureID)tile_textures_array_view_individual[tile_index], min + padding, ImVec2(max.x - padding.x, min.y + padding.y), max - padding, ImVec2(min.x + padding.x, max.y - padding.y), ImVec2(uv1.x, uv0.y), ImVec2(uv1.x, uv1.y), ImVec2(uv0.x, uv1.y), ImVec2(uv0.x, uv0.y)); break; - case 2: context->CurrentWindow->DrawList->AddImageQuad((ImTextureID)tile_textures_array_view_individual[tile_index], min + padding, ImVec2(max.x - padding.x, min.y + padding.y), max - padding, ImVec2(min.x + padding.x, max.y - padding.y), ImVec2(uv1.x, uv1.y), ImVec2(uv0.x, uv1.y), ImVec2(uv0.x, uv0.y), ImVec2(uv1.x, uv0.y)); break; - case 3: context->CurrentWindow->DrawList->AddImageQuad((ImTextureID)tile_textures_array_view_individual[tile_index], min + padding, ImVec2(max.x - padding.x, min.y + padding.y), max - padding, ImVec2(min.x + padding.x, max.y - padding.y), ImVec2(uv0.x, uv1.y), ImVec2(uv0.x, uv0.y), ImVec2(uv1.x, uv0.y), ImVec2(uv1.x, uv1.y)); break; + case 0: context->CurrentWindow->DrawList->AddImageQuad((ImTextureID)tile_textures_atlas_view_unorm, min + padding, ImVec2(max.x - padding.x, min.y + padding.y), max - padding, ImVec2(min.x + padding.x, max.y - padding.y), ImVec2(uv.x, uv.y), ImVec2(uv.z, uv.y), ImVec2(uv.z, uv.w), ImVec2(uv.x, uv.w)); break; + case 1: context->CurrentWindow->DrawList->AddImageQuad((ImTextureID)tile_textures_atlas_view_unorm, min + padding, ImVec2(max.x - padding.x, min.y + padding.y), max - padding, ImVec2(min.x + padding.x, max.y - padding.y), ImVec2(uv.z, uv.y), ImVec2(uv.z, uv.w), ImVec2(uv.x, uv.w), ImVec2(uv.x, uv.y)); break; + case 2: context->CurrentWindow->DrawList->AddImageQuad((ImTextureID)tile_textures_atlas_view_unorm, min + padding, ImVec2(max.x - padding.x, min.y + padding.y), max - padding, ImVec2(min.x + padding.x, max.y - padding.y), ImVec2(uv.z, uv.w), ImVec2(uv.x, uv.w), ImVec2(uv.x, uv.y), ImVec2(uv.z, uv.y)); break; + case 3: context->CurrentWindow->DrawList->AddImageQuad((ImTextureID)tile_textures_atlas_view_unorm, min + padding, ImVec2(max.x - padding.x, min.y + padding.y), max - padding, ImVec2(min.x + padding.x, max.y - padding.y), ImVec2(uv.x, uv.w), ImVec2(uv.x, uv.y), ImVec2(uv.z, uv.y), ImVec2(uv.z, uv.w)); break; default: SDL_assert_always(false); break; } @@ -856,7 +862,7 @@ static bool recreate_graphics_pipelines() { .texture = { .sampleType = WGPUTextureSampleType_Float, - .viewDimension = WGPUTextureViewDimension_2DArray, + .viewDimension = WGPUTextureViewDimension_2D, .multisampled = false, }, }, @@ -878,6 +884,16 @@ static bool recreate_graphics_pipelines() { .minBindingSize = 0, }, }, + { + .binding = 3, + .visibility = WGPUShaderStage_Fragment, + + .buffer = { + .type = WGPUBufferBindingType_ReadOnlyStorage, + .hasDynamicOffset = false, + .minBindingSize = 0, + }, + }, }; WGPUBindGroupLayoutDescriptor world_bind_group_layout_descriptor = { @@ -1098,10 +1114,10 @@ static bool recreate_tile_textures() { }; WGPUTextureDescriptor descriptor = { - .label = { .data = "tile_textures_array", .length = WGPU_STRLEN }, + .label = { .data = "tile_textures_atlas", .length = WGPU_STRLEN }, .usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst, .dimension = WGPUTextureDimension_2D, - .size = { .width = TILE_SIZE, .height = TILE_SIZE, .depthOrArrayLayers = SDL_arraysize(tile_infos) }, + .size = { .width = TILE_ATLAS_SIZE, .height = TILE_ATLAS_SIZE, .depthOrArrayLayers = 1 }, .format = WGPUTextureFormat_RGBA8UnormSrgb, .mipLevelCount = 1, .sampleCount = 1, @@ -1109,24 +1125,21 @@ static bool recreate_tile_textures() { .viewFormats = view_formats, }; - tile_textures_array = wgpuDeviceCreateTexture(device, &descriptor); - if (!tile_textures_array) { + tile_textures_atlas = wgpuDeviceCreateTexture(device, &descriptor); + if (!tile_textures_atlas) { log_error("Failed to create texture."); - return WGPUOptionalBool_False; + return false; } - tile_textures_array_view = wgpuTextureCreateView(tile_textures_array, NULL); + tile_textures_atlas_view = wgpuTextureCreateView(tile_textures_atlas, NULL); - tile_textures_array_view_individual = (WGPUTextureView *)malloc(SDL_arraysize(tile_infos) * sizeof(WGPUTextureView)); - for (Uint32 i = 0; i < SDL_arraysize(tile_infos); i++) { - WGPUTextureViewDescriptor descriptor = { + WGPUTextureViewDescriptor unorm_view_descriptor = { .format = WGPUTextureFormat_RGBA8Unorm, .dimension = WGPUTextureViewDimension_2D, .mipLevelCount = 1, - .baseArrayLayer = i, + .baseArrayLayer = 0, .arrayLayerCount = 1, }; - tile_textures_array_view_individual[i] = wgpuTextureCreateView(tile_textures_array, &descriptor); - } + tile_textures_atlas_view_unorm = wgpuTextureCreateView(tile_textures_atlas, &unorm_view_descriptor); for (Uint32 i = 0; i < SDL_arraysize(tile_infos); i++) { char path[256] = ASSETS_PATH; @@ -1136,17 +1149,25 @@ static bool recreate_tile_textures() { stbi_uc *data = stbi_load(path, &width, &height, NULL, 4); if (!data) { log_error("Failed to load texture (\"%s\"). Exiting.", path); - wgpuTextureRelease(tile_textures_array); + wgpuTextureRelease(tile_textures_atlas); return false; } SDL_assert_always(width == TILE_SIZE); SDL_assert_always(height == TILE_SIZE); + Uint32 x = (i * TILE_SIZE) % TILE_ATLAS_SIZE; + Uint32 y = ((i * TILE_SIZE) / TILE_ATLAS_SIZE) * TILE_SIZE; + + tile_uvs[i].x = x; + tile_uvs[i].y = y; + tile_uvs[i].z = x + TILE_SIZE; + tile_uvs[i].w = y + TILE_SIZE; + WGPUTexelCopyTextureInfo texel_copy_texture_info = { - .texture = tile_textures_array, + .texture = tile_textures_atlas, .mipLevel = 0, - .origin = { .x = 0, .y = 0, .z = i }, + .origin = { .x = x, .y = y, .z = 0 }, .aspect = WGPUTextureAspect_All, }; @@ -1166,6 +1187,8 @@ static bool recreate_tile_textures() { stbi_image_free(data); } + tile_uvs_buffer = create_buffer(WGPUBufferUsage_Storage | WGPUBufferUsage_CopyDst, sizeof(tile_uvs), tile_uvs, "tile_uvs_buffer"); + return true; } @@ -1445,7 +1468,7 @@ static bool init_webgpu() { .minFilter = WGPUFilterMode_Linear, .mipmapFilter = WGPUMipmapFilterMode_Linear, - .maxAnisotropy = 16, + .maxAnisotropy = 1, }; pixel_sampler = wgpuDeviceCreateSampler(device, &pixel_sampler_descriptor); @@ -1525,9 +1548,10 @@ static bool init_webgpu() { per_frame_bind_group = wgpuDeviceCreateBindGroup(device, &per_frame_bind_group_descriptor); WGPUBindGroupEntry world_bind_group_entries[] = { - { .binding = 0, .textureView = tile_textures_array_view }, + { .binding = 0, .textureView = tile_textures_atlas_view }, { .binding = 1, .sampler = pixel_sampler }, { .binding = 2, .buffer = tint_color_buffer, .offset = 0, .size = WGPU_WHOLE_SIZE }, + { .binding = 3, .buffer = tile_uvs_buffer, .offset = 0, .size = WGPU_WHOLE_SIZE }, }; WGPUBindGroupDescriptor world_bind_group_descriptor = { @@ -1869,13 +1893,10 @@ int main(int argc, char **argv) { for (int i = 0; i < SDL_arraysize(tile_infos); i++) { ImGui::PushID(i); - ImVec2 uv0 = ImVec2(0, 0); - ImVec2 uv1 = ImVec2(1, 1); - if (i != 0) SameLineOrWrap(ImVec2(32, 32)); - if (SelectableTile("##tile", selected_tile == i, i, ImVec2(32, 32), uv0, uv1, SDL_max(selected_rotation, 0))) { + if (SelectableTile("##tile", selected_tile == i, i, ImVec2(32, 32), SDL_max(selected_rotation, 0))) { selected_tile_kind = -1; selected_tile = i; } @@ -1885,24 +1906,21 @@ int main(int argc, char **argv) { if (selected_tile != -1) { ImGui::Text("Rotation:"); - - ImVec2 uv0 = ImVec2(0, 0); - ImVec2 uv1 = ImVec2(1, 1); - if (SelectableTile("##None", selected_rotation == 0, selected_tile, ImVec2(32, 32), uv0, uv1, 0)) + if (SelectableTile("##None", selected_rotation == 0, selected_tile, ImVec2(32, 32), 0)) selected_rotation = 0; SameLineOrWrap(ImVec2(32, 32)); - if (SelectableTile("##90", selected_rotation == 1, selected_tile, ImVec2(32, 32), uv0, uv1, 1)) + if (SelectableTile("##90", selected_rotation == 1, selected_tile, ImVec2(32, 32), 1)) selected_rotation = 1; SameLineOrWrap(ImVec2(32, 32)); - if (SelectableTile("##180", selected_rotation == 2, selected_tile, ImVec2(32, 32), uv0, uv1, 2)) + if (SelectableTile("##180", selected_rotation == 2, selected_tile, ImVec2(32, 32), 2)) selected_rotation = 2; SameLineOrWrap(ImVec2(32, 32)); - if (SelectableTile("##270", selected_rotation == 3, selected_tile, ImVec2(32, 32), uv0, uv1, 3)) + if (SelectableTile("##270", selected_rotation == 3, selected_tile, ImVec2(32, 32), 3)) selected_rotation = 3; if (ImGui::Selectable("Random", selected_rotation == -1)) @@ -1994,9 +2012,10 @@ int main(int argc, char **argv) { if (event.key.key == SDLK_F5) { recreate_tile_textures(); WGPUBindGroupEntry world_bind_group_entries[] = { - { .binding = 0, .textureView = tile_textures_array_view }, + { .binding = 0, .textureView = tile_textures_atlas_view }, { .binding = 1, .sampler = pixel_sampler }, { .binding = 2, .buffer = tint_color_buffer, .offset = 0, .size = WGPU_WHOLE_SIZE }, + { .binding = 3, .buffer = tile_uvs_buffer, .offset = 0, .size = WGPU_WHOLE_SIZE }, }; WGPUBindGroupDescriptor world_bind_group_descriptor = { diff --git a/src/shaders/world.wgsl b/src/shaders/world.wgsl index 131966a..27f8cf5 100644 --- a/src/shaders/world.wgsl +++ b/src/shaders/world.wgsl @@ -37,9 +37,9 @@ struct Per_Frame_Data { var output: VertexShaderOutput; let tile_type = extractBits(input.tile_info, 0, 16); - let rotation = extractBits(input.tile_info, 16, 2); + let rotation = extractBits(input.tile_info, 16, 2); - let tile_pos = vec2(f32(input.instance_index % per_frame.map_width), f32(input.instance_index / per_frame.map_width)) - vec2(0.5, 0.5); + let tile_pos = vec2(f32(input.instance_index % per_frame.map_width), f32(input.instance_index / per_frame.map_width)) - vec2(0.5, 0.5); output.tile = tile_type; output.pos = vec4(tile_pos + input.pos.xy, 0, 1) * view_projection_matrix; @@ -55,11 +55,13 @@ struct Per_Frame_Data { return output; } -@group(1) @binding(0) var texture1: texture_2d_array; +@group(1) @binding(0) var texture1: texture_2d; @group(1) @binding(1) var sampler1: sampler; @group(1) @binding(2) var tint: vec3; +@group(1) @binding(3) var tile_uvs: array>; + @fragment fn main_fragment(input: VertexShaderOutput) -> FragmentShaderOutput { var output: FragmentShaderOutput; @@ -69,11 +71,15 @@ struct Per_Frame_Data { return output; } -fn pixel_art_sample(input_texture: texture_2d_array, input_sampler: sampler, input_uv: vec2, index: u32) -> vec4 { +fn pixel_art_sample(input_texture: texture_2d, input_sampler: sampler, input_uv: vec2, tile: u32) -> vec4 { let dimensions = vec2(textureDimensions(input_texture)); - let texture_uv = input_uv * dimensions.xy; - let sample_uv = (floor(texture_uv) + min(fract(texture_uv) / fwidth(texture_uv), vec2(1.0, 1.0)) - 0.5) / dimensions.xy; + let tile_uv = tile_uvs[tile]; + + let texture_uv = mix(tile_uv.xy, tile_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, (tile_uv.xy + 0.5) / dimensions, (tile_uv.zw - 0.5) / dimensions); - return textureSample(input_texture, input_sampler, sample_uv, index); + return textureSample(input_texture, input_sampler, uv); }