change world tiles texture from texture_2d_array to an atlas texture_2d

This commit is contained in:
Sven Balzer 2026-04-02 17:52:28 +02:00
parent 9dd37f6d40
commit b549728a24
2 changed files with 72 additions and 47 deletions

View File

@ -24,7 +24,9 @@ using namespace M;
#define ASSETS_PATH "./assets/" #define ASSETS_PATH "./assets/"
#define NEAR_PLANE (0.01f) #define NEAR_PLANE (0.01f)
#define TILE_SIZE (32)
#define TILE_SIZE (32)
#define TILE_ATLAS_SIZE (256)
static SDL_Window *window; static SDL_Window *window;
static bool wgpu_init_done; static bool wgpu_init_done;
@ -47,9 +49,9 @@ static WGPUBindGroup basic_bind_group;
static WGPUTexture player_texture; static WGPUTexture player_texture;
static WGPUTextureView player_texture_view; static WGPUTextureView player_texture_view;
static WGPUTexture tile_textures_array; static WGPUTexture tile_textures_atlas;
static WGPUTextureView tile_textures_array_view; static WGPUTextureView tile_textures_atlas_view;
static WGPUTextureView *tile_textures_array_view_individual; static WGPUTextureView tile_textures_atlas_view_unorm;
static WGPUBuffer view_projection_matrix_buffer; static WGPUBuffer view_projection_matrix_buffer;
static WGPUBuffer per_frame_buffer; static WGPUBuffer per_frame_buffer;
@ -60,7 +62,7 @@ static WGPUBuffer index_buffer;
static WGPUBuffer grid_vertex_buffer; static WGPUBuffer grid_vertex_buffer;
static WGPUBuffer grid_index_buffer; static WGPUBuffer grid_index_buffer;
static WGPUBuffer player_instance_buffer; static WGPUBuffer player_instance_buffer;
static WGPUBuffer tile_infos_buffer; static WGPUBuffer tile_uvs_buffer;
static WGPUSurfaceConfiguration surface_configuration; 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 ) }, { 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_kind = -1;
static Sint32 selected_tile = -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); 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 ImGuiContext *context = ImGui::GetCurrentContext();
const ImVec2 padding = context->Style.FramePadding; 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 min = ImGui::GetItemRectMin();
ImVec2 max = ImGui::GetItemRectMax(); ImVec2 max = ImGui::GetItemRectMax();
V4 uv = tile_uvs[tile_index] / TILE_ATLAS_SIZE;
switch (orientation) { 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 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_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 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_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 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_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 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; default: SDL_assert_always(false); break;
} }
@ -856,7 +862,7 @@ static bool recreate_graphics_pipelines() {
.texture = { .texture = {
.sampleType = WGPUTextureSampleType_Float, .sampleType = WGPUTextureSampleType_Float,
.viewDimension = WGPUTextureViewDimension_2DArray, .viewDimension = WGPUTextureViewDimension_2D,
.multisampled = false, .multisampled = false,
}, },
}, },
@ -878,6 +884,16 @@ static bool recreate_graphics_pipelines() {
.minBindingSize = 0, .minBindingSize = 0,
}, },
}, },
{
.binding = 3,
.visibility = WGPUShaderStage_Fragment,
.buffer = {
.type = WGPUBufferBindingType_ReadOnlyStorage,
.hasDynamicOffset = false,
.minBindingSize = 0,
},
},
}; };
WGPUBindGroupLayoutDescriptor world_bind_group_layout_descriptor = { WGPUBindGroupLayoutDescriptor world_bind_group_layout_descriptor = {
@ -1098,10 +1114,10 @@ static bool recreate_tile_textures() {
}; };
WGPUTextureDescriptor descriptor = { WGPUTextureDescriptor descriptor = {
.label = { .data = "tile_textures_array", .length = WGPU_STRLEN }, .label = { .data = "tile_textures_atlas", .length = WGPU_STRLEN },
.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst, .usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst,
.dimension = WGPUTextureDimension_2D, .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, .format = WGPUTextureFormat_RGBA8UnormSrgb,
.mipLevelCount = 1, .mipLevelCount = 1,
.sampleCount = 1, .sampleCount = 1,
@ -1109,24 +1125,21 @@ static bool recreate_tile_textures() {
.viewFormats = view_formats, .viewFormats = view_formats,
}; };
tile_textures_array = wgpuDeviceCreateTexture(device, &descriptor); tile_textures_atlas = wgpuDeviceCreateTexture(device, &descriptor);
if (!tile_textures_array) { if (!tile_textures_atlas) {
log_error("Failed to create texture."); 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)); WGPUTextureViewDescriptor unorm_view_descriptor = {
for (Uint32 i = 0; i < SDL_arraysize(tile_infos); i++) {
WGPUTextureViewDescriptor descriptor = {
.format = WGPUTextureFormat_RGBA8Unorm, .format = WGPUTextureFormat_RGBA8Unorm,
.dimension = WGPUTextureViewDimension_2D, .dimension = WGPUTextureViewDimension_2D,
.mipLevelCount = 1, .mipLevelCount = 1,
.baseArrayLayer = i, .baseArrayLayer = 0,
.arrayLayerCount = 1, .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++) { for (Uint32 i = 0; i < SDL_arraysize(tile_infos); i++) {
char path[256] = ASSETS_PATH; char path[256] = ASSETS_PATH;
@ -1136,17 +1149,25 @@ static bool recreate_tile_textures() {
stbi_uc *data = stbi_load(path, &width, &height, NULL, 4); stbi_uc *data = stbi_load(path, &width, &height, NULL, 4);
if (!data) { if (!data) {
log_error("Failed to load texture (\"%s\"). Exiting.", path); log_error("Failed to load texture (\"%s\"). Exiting.", path);
wgpuTextureRelease(tile_textures_array); wgpuTextureRelease(tile_textures_atlas);
return false; return false;
} }
SDL_assert_always(width == TILE_SIZE); SDL_assert_always(width == TILE_SIZE);
SDL_assert_always(height == 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 = { WGPUTexelCopyTextureInfo texel_copy_texture_info = {
.texture = tile_textures_array, .texture = tile_textures_atlas,
.mipLevel = 0, .mipLevel = 0,
.origin = { .x = 0, .y = 0, .z = i }, .origin = { .x = x, .y = y, .z = 0 },
.aspect = WGPUTextureAspect_All, .aspect = WGPUTextureAspect_All,
}; };
@ -1166,6 +1187,8 @@ static bool recreate_tile_textures() {
stbi_image_free(data); stbi_image_free(data);
} }
tile_uvs_buffer = create_buffer(WGPUBufferUsage_Storage | WGPUBufferUsage_CopyDst, sizeof(tile_uvs), tile_uvs, "tile_uvs_buffer");
return true; return true;
} }
@ -1445,7 +1468,7 @@ static bool init_webgpu() {
.minFilter = WGPUFilterMode_Linear, .minFilter = WGPUFilterMode_Linear,
.mipmapFilter = WGPUMipmapFilterMode_Linear, .mipmapFilter = WGPUMipmapFilterMode_Linear,
.maxAnisotropy = 16, .maxAnisotropy = 1,
}; };
pixel_sampler = wgpuDeviceCreateSampler(device, &pixel_sampler_descriptor); 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); per_frame_bind_group = wgpuDeviceCreateBindGroup(device, &per_frame_bind_group_descriptor);
WGPUBindGroupEntry world_bind_group_entries[] = { 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 = 1, .sampler = pixel_sampler },
{ .binding = 2, .buffer = tint_color_buffer, .offset = 0, .size = WGPU_WHOLE_SIZE }, { .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 = { 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++) { for (int i = 0; i < SDL_arraysize(tile_infos); i++) {
ImGui::PushID(i); ImGui::PushID(i);
ImVec2 uv0 = ImVec2(0, 0);
ImVec2 uv1 = ImVec2(1, 1);
if (i != 0) if (i != 0)
SameLineOrWrap(ImVec2(32, 32)); 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_kind = -1;
selected_tile = i; selected_tile = i;
} }
@ -1885,24 +1906,21 @@ int main(int argc, char **argv) {
if (selected_tile != -1) { if (selected_tile != -1) {
ImGui::Text("Rotation:"); 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; selected_rotation = 0;
SameLineOrWrap(ImVec2(32, 32)); 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; selected_rotation = 1;
SameLineOrWrap(ImVec2(32, 32)); 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; selected_rotation = 2;
SameLineOrWrap(ImVec2(32, 32)); 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; selected_rotation = 3;
if (ImGui::Selectable("Random", selected_rotation == -1)) if (ImGui::Selectable("Random", selected_rotation == -1))
@ -1994,9 +2012,10 @@ int main(int argc, char **argv) {
if (event.key.key == SDLK_F5) { if (event.key.key == SDLK_F5) {
recreate_tile_textures(); recreate_tile_textures();
WGPUBindGroupEntry world_bind_group_entries[] = { 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 = 1, .sampler = pixel_sampler },
{ .binding = 2, .buffer = tint_color_buffer, .offset = 0, .size = WGPU_WHOLE_SIZE }, { .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 = { WGPUBindGroupDescriptor world_bind_group_descriptor = {

View File

@ -37,9 +37,9 @@ struct Per_Frame_Data {
var output: VertexShaderOutput; var output: VertexShaderOutput;
let tile_type = extractBits(input.tile_info, 0, 16); 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>(f32(input.instance_index % per_frame.map_width), f32(input.instance_index / per_frame.map_width)) - vec2<f32>(0.5, 0.5); let tile_pos = vec2<f32>(f32(input.instance_index % per_frame.map_width), f32(input.instance_index / per_frame.map_width)) - vec2<f32>(0.5, 0.5);
output.tile = tile_type; output.tile = tile_type;
output.pos = vec4<f32>(tile_pos + input.pos.xy, 0, 1) * view_projection_matrix; output.pos = vec4<f32>(tile_pos + input.pos.xy, 0, 1) * view_projection_matrix;
@ -55,11 +55,13 @@ struct Per_Frame_Data {
return output; return output;
} }
@group(1) @binding(0) var texture1: texture_2d_array<f32>; @group(1) @binding(0) var texture1: texture_2d<f32>;
@group(1) @binding(1) var sampler1: sampler; @group(1) @binding(1) var sampler1: sampler;
@group(1) @binding(2) var<uniform> tint: vec3<f32>; @group(1) @binding(2) var<uniform> tint: vec3<f32>;
@group(1) @binding(3) var<storage> tile_uvs: array<vec4<f32>>;
@fragment fn main_fragment(input: VertexShaderOutput) -> FragmentShaderOutput { @fragment fn main_fragment(input: VertexShaderOutput) -> FragmentShaderOutput {
var output: FragmentShaderOutput; var output: FragmentShaderOutput;
@ -69,11 +71,15 @@ struct Per_Frame_Data {
return output; return output;
} }
fn pixel_art_sample(input_texture: texture_2d_array<f32>, input_sampler: sampler, input_uv: vec2<f32>, index: u32) -> vec4<f32> { fn pixel_art_sample(input_texture: texture_2d<f32>, input_sampler: sampler, input_uv: vec2<f32>, tile: u32) -> vec4<f32> {
let dimensions = vec2<f32>(textureDimensions(input_texture)); let dimensions = vec2<f32>(textureDimensions(input_texture));
let texture_uv = input_uv * dimensions.xy; let tile_uv = tile_uvs[tile];
let sample_uv = (floor(texture_uv) + min(fract(texture_uv) / fwidth(texture_uv), vec2<f32>(1.0, 1.0)) - 0.5) / dimensions.xy;
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);
} }