#include #include #include #include #include #include #include #include #include #include #include #include #include "defer.h" #include "log.h" #include "math_graphics.h" #include "stb_image.h" #include "shaders/shaders.h" using namespace M; #define ASSETS_PATH "../assets/" #define NEAR_PLANE (0.01f) #define TILE_SIZE (32) static SDL_Window *window; static bool wgpu_init_done; static WGPUInstance instance; static WGPUDevice device; static WGPUQueue queue; static WGPUSurface surface; static WGPUTexture framebuffer; static WGPURenderPipeline basic_render_pipeline; static WGPURenderPipeline world_render_pipeline; static WGPURenderPipeline grid_render_pipeline; static WGPUSampler pixel_sampler; static WGPUBindGroup per_frame_bind_group; static WGPUBindGroup world_bind_group; static WGPUBindGroup basic_bind_group; static WGPUTexture tile_textures_array; static WGPUTextureView tile_textures_array_view; static WGPUTextureView *tile_textures_array_view_individual; static WGPUBuffer view_projection_matrix_buffer; static WGPUBuffer per_frame_buffer; static WGPUBuffer tint_color_buffer; static WGPUBuffer vertex_buffer; 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 Sint32 window_width; static Sint32 window_height; static Mix_Music *music_setting_off_piano; static float volume_master = 100.0f; static float volume_music = 100.0f; static float volume_sfx = 100.0f; static bool Running = true; static float camera_fovy_degrees = 31.0f; static float camera_tilt = 25.5f; static float camera_distance = 13.5f; static M4x4 view_matrix; static M4x4 inverse_view_matrix; static M4x4 projection_matrix; static M4x4 inverse_projection_matrix; static SDL_Time time; static bool use_actual_time = true; static SDL_DateTime calendar_time; static V2 mouse_pos; #define MAX_TINT_TIMES 32 static int num_used_tint_times = 4; static int time_tints_times[MAX_TINT_TIMES][3] = { { 4, 0, 0 }, { 9, 0, 0 }, { 19, 0, 0 }, { 21, 0, 0 }, }; static V3 time_tints[MAX_TINT_TIMES] = { V3_(0.314f, 0.369f, 0.455f), V3_(1.0f, 0.891f, 0.868f), V3_(1.0f, 0.465f, 0.373f), V3_(0.314f, 0.369f, 0.455f), }; enum Settings_Category { SETTINGS_UNKNOWN, SETTINGS_AUDIO, }; struct Vertex { V3 pos; V2 uv; }; static Vertex vertices[] = { {{ -0.5f, 0.5f }, { 0.0f, 0.0f }}, {{ -0.5f, -0.5f }, { 0.0f, 1.0f }}, {{ 0.5f, -0.5f }, { 1.0f, 1.0f }}, {{ 0.5f, 0.5f }, { 1.0f, 0.0f }}, }; static Uint16 indices[] = { 0, 1, 2, 0, 2, 3, }; #define grid_line_size (1.0f / 64.0f) static Vertex grid_vertices[] = { // LEFT {{ -0.5f, 0.5f }}, {{ -0.5f, -0.5f + grid_line_size }}, {{ -0.5f + grid_line_size, -0.5f + grid_line_size }}, {{ -0.5f + grid_line_size, 0.5f }}, // TOP {{ -0.5f + grid_line_size, 0.5f }}, {{ -0.5f + grid_line_size, 0.5f - grid_line_size }}, {{ 0.5f, 0.5f - grid_line_size }}, {{ 0.5f, 0.5f }}, // RIGHT {{ 0.5f - grid_line_size, 0.5f - grid_line_size }}, {{ 0.5f - grid_line_size, -0.5f }}, {{ 0.5f, -0.5f }}, {{ 0.5f, 0.5f - grid_line_size }}, // BOTTOM {{ -0.5f, -0.5f + grid_line_size }}, {{ -0.5f, -0.5f }}, {{ 0.5f - grid_line_size, -0.5f }}, {{ 0.5f - grid_line_size, -0.5f + grid_line_size }}, }; static Uint16 grid_indices[] = { // LEFT 0, 1, 2, 0, 2, 3, // TOP 4, 5, 6, 4, 6, 7, // RIGHT 8, 9, 10, 8, 10, 11, // BOTTOM 12, 13, 14, 12, 14, 15, }; struct Instance { V2 pos; }; static Instance player_instance = {{ 0.0f, 0.0f }}; struct Map { Sint32 width; Sint32 height; Uint32 *tiles; char *name; WGPUBuffer gpu_buffer; }; static Map current_map; struct Player { Sint32 pos_x; Sint32 pos_y; }; static Player player; typedef struct Sint32x2 { Sint32 x; Sint32 y; } Sint32x2; struct PerFrame { Sint32x2 drag_start; Sint32x2 mouse; V2 grid_offset; Sint32 grid_width; Sint32 map_width; }; static PerFrame per_frame = {}; typedef enum : Uint8 { TILEKIND_ERROR = 0, TILEKIND_NONE = 1, TILEKIND_GRASS = 2, TILEKIND_GROUND = 3, TILEKIND_WATER = 4, } TileKind; #define TILE_CORNER_INFO(top_left, top_right, bottom_right, bottom_left) (((top_left) << 24) | ((top_right) << 16) | ((bottom_right) << 8) | bottom_left) typedef struct { Uint16 type; const char *asset_path; Uint32 corner_info; } TileInfo; static TileInfo tile_infos[] = { { 0x0001, "tiles/error.png", TILE_CORNER_INFO(TILEKIND_ERROR, TILEKIND_ERROR, TILEKIND_ERROR, TILEKIND_ERROR ) }, { 0x0000, "tiles/empty.png", TILE_CORNER_INFO(TILEKIND_NONE, TILEKIND_NONE, TILEKIND_NONE, TILEKIND_NONE ) }, { 0x0102, "tiles/grass_3.png", TILE_CORNER_INFO(TILEKIND_GRASS, TILEKIND_GRASS, TILEKIND_GRASS, TILEKIND_GRASS ) }, { 0x0100, "tiles/grass_1.png", TILE_CORNER_INFO(TILEKIND_GRASS, TILEKIND_GRASS, TILEKIND_GRASS, TILEKIND_GRASS ) }, { 0x0101, "tiles/grass_2.png", TILE_CORNER_INFO(TILEKIND_GRASS, TILEKIND_GRASS, TILEKIND_GRASS, TILEKIND_GRASS ) }, { 0x0103, "tiles/grass_4.png", TILE_CORNER_INFO(TILEKIND_GRASS, TILEKIND_GRASS, TILEKIND_GRASS, TILEKIND_GRASS ) }, { 0x0202, "tiles/ground_3.png", TILE_CORNER_INFO(TILEKIND_GROUND, TILEKIND_GROUND, TILEKIND_GROUND, TILEKIND_GROUND ) }, { 0x0200, "tiles/ground_1.png", TILE_CORNER_INFO(TILEKIND_GROUND, TILEKIND_GROUND, TILEKIND_GROUND, TILEKIND_GROUND ) }, { 0x0201, "tiles/ground_2.png", TILE_CORNER_INFO(TILEKIND_GROUND, TILEKIND_GROUND, TILEKIND_GROUND, TILEKIND_GROUND ) }, { 0x0300, "tiles/water_1.png", TILE_CORNER_INFO(TILEKIND_WATER, TILEKIND_WATER, TILEKIND_WATER, TILEKIND_WATER ) }, { 0x0301, "tiles/water_2.png", TILE_CORNER_INFO(TILEKIND_WATER, TILEKIND_WATER, TILEKIND_WATER, TILEKIND_WATER ) }, { 0x0400, "tiles/grass_ground_1.png", TILE_CORNER_INFO(TILEKIND_GROUND, TILEKIND_GROUND, TILEKIND_GRASS, TILEKIND_GRASS ) }, { 0x0401, "tiles/grass_ground_2.png", TILE_CORNER_INFO(TILEKIND_GROUND, TILEKIND_GROUND, TILEKIND_GRASS, TILEKIND_GRASS ) }, { 0x0402, "tiles/grass_ground_3.png", TILE_CORNER_INFO(TILEKIND_GROUND, TILEKIND_GROUND, TILEKIND_GRASS, TILEKIND_GRASS ) }, { 0x0410, "tiles/grass_ground_outer_corner.png", TILE_CORNER_INFO(TILEKIND_GROUND, TILEKIND_GROUND, TILEKIND_GRASS, TILEKIND_GROUND ) }, { 0x0411, "tiles/grass_ground_outer_corner_2.png", TILE_CORNER_INFO(TILEKIND_GROUND, TILEKIND_GROUND, TILEKIND_GRASS, TILEKIND_GROUND ) }, { 0x0420, "tiles/grass_ground_inner_corner.png", TILE_CORNER_INFO(TILEKIND_GRASS, TILEKIND_GROUND, TILEKIND_GRASS, TILEKIND_GRASS ) }, { 0x0421, "tiles/grass_ground_inner_corner_2.png", TILE_CORNER_INFO(TILEKIND_GRASS, TILEKIND_GROUND, TILEKIND_GRASS, TILEKIND_GRASS ) }, { 0x0422, "tiles/grass_ground_inner_corner_3.png", TILE_CORNER_INFO(TILEKIND_GRASS, TILEKIND_GROUND, TILEKIND_GRASS, TILEKIND_GRASS ) }, { 0x0423, "tiles/grass_ground_two_corner.png", TILE_CORNER_INFO(TILEKIND_GRASS, TILEKIND_GROUND, TILEKIND_GRASS, TILEKIND_GROUND ) }, }; static V4 cpu_tile_infos_buffer[SDL_arraysize(tile_infos)]; static Sint32 selected_tile_kind = -1; static Sint32 selected_tile = -1; static Sint32 selected_rotation = 0; static bool dragging_tile_change = false; static V2 drag_start_pos; static bool update_buffer(WGPUBuffer buffer, Uint32 offset, Uint32 num_bytes, void *data) { wgpuQueueWriteBuffer(queue, buffer, offset, data, num_bytes); return true; } static WGPUBuffer create_buffer(WGPUBufferUsage usage, Uint32 num_bytes, void *data = NULL, const char *name = NULL) { WGPUBufferDescriptor descriptor = { .label = { .data = name, .length = WGPU_STRLEN }, .usage = usage, .size = num_bytes, .mappedAtCreation = data != NULL, }; WGPUBuffer buffer = wgpuDeviceCreateBuffer(device, &descriptor); if (data) { void *mapped_data = wgpuBufferGetMappedRange(buffer, 0, num_bytes); memcpy(mapped_data, data, num_bytes); wgpuBufferUnmap(buffer); } return buffer; } static bool save_map(Map map) { char path[256] = ASSETS_PATH "maps/"; SDL_strlcat(path, map.name, SDL_arraysize(path)); SDL_IOStream *file = SDL_IOFromFile(path, "wb"); if (!file) { log_error("Failed to open map file for writing."); return false; } defer(SDL_CloseIO(file)); if (!SDL_WriteS32LE(file, map.width)) { log_error("Failed to write width to map file."); return false; } if (!SDL_WriteS32LE(file, map.height)) { log_error("Failed to write height to map file."); return false; } for (int i = 0; i < map.width * map.height; i++) { Uint32 type = tile_infos[map.tiles[i] & 0xffff].type; Uint32 orientation = map.tiles[i] & 0x30000; Uint32 to_write = orientation | type; if (!SDL_WriteU32LE(file, to_write)) { log_error("Failed to write tile to map file."); return false; } } if(!SDL_FlushIO(file)) { log_error("Failed to flush data to map file."); return false; }; log("Saved map file."); return true; } static bool load_map(const char *name, Map *result) { char path[256] = ASSETS_PATH "maps/"; SDL_strlcat(path, name, SDL_arraysize(path)); SDL_IOStream *file = SDL_IOFromFile(path, "rb"); if (!file) { log_error("Failed to open map file for reading."); return false; } defer(SDL_CloseIO(file)); result->name = SDL_strdup(name); if (!SDL_ReadS32LE(file, &result->width)) { log_error("Failed read width from map file."); return false; } if (!SDL_ReadS32LE(file, &result->height)) { log_error("Failed read height from map file."); return false; } result->tiles = (Uint32*)malloc(result->width * result->height * sizeof(Uint32)); for (int i = 0; i < result->width * result->height; i++) { Uint32 tile = 0; if (!SDL_ReadU32LE(file, &tile)) { free(result->tiles); return false; } Uint32 type = tile & 0xffff; Uint32 orientation = tile & 0x30000; Uint32 kind = 0; for (int i = 0; i < SDL_arraysize(tile_infos); i++) { if (tile_infos[i].type == type) { kind = i; break; } } result->tiles[i] = orientation | kind; } char buffer_name[256] = "Map "; SDL_strlcat(buffer_name, result->name, SDL_arraysize(buffer_name)); result->gpu_buffer = create_buffer(WGPUBufferUsage_Vertex | WGPUBufferUsage_CopyDst, result->width * result->height * 4, result->tiles, buffer_name); if (!result->gpu_buffer) { log_error("Failed to create buffer. Exiting."); return 1; } log("Loaded map file."); return true; } static void unload_map(Map *map) { map->width = 0; map->height = 0; free(map->tiles); SDL_free(map->name); wgpuBufferRelease(map->gpu_buffer); } static void change_map_size(Map *map, char direction, int amount) { WGPUBuffer old_gpu_buffer = map->gpu_buffer; Uint32 *old_map = map->tiles; Sint32 old_map_width = map->width; Sint32 old_map_height = map->height; Sint32 new_x_offset = 0; Sint32 new_y_offset = 0; Sint32 old_x_offset = 0; Sint32 old_y_offset = 0; Sint32 to_fill_width = map->width; Sint32 to_fill_height = map->height; Sint32 to_fill_x_offset = 0; Sint32 to_fill_y_offset = 0; if (direction == 'W') { player.pos_x = player.pos_x + amount; map->width += amount; to_fill_width = amount; if (amount < 0) old_x_offset = -amount; else new_x_offset = amount; } if (direction == 'N') { player.pos_y = player.pos_y + amount; map->height += amount; to_fill_height = amount; if (amount < 0) old_y_offset = -amount; else new_y_offset = amount; } if (direction == 'E') { map->width += amount; to_fill_width = amount; to_fill_x_offset = old_map_width; } if (direction == 'S') { map->height += amount; to_fill_height = amount; to_fill_y_offset = old_map_height; } map->tiles = (Uint32*)malloc(map->width * map->height * sizeof(Uint32)); for (int y = 0; y < min(old_map_height, map->height); y++) { for (int x = 0; x < min(old_map_width, map->width); x++) { map->tiles[(y + new_y_offset) * map->width + (x + new_x_offset)] = old_map[(y + old_y_offset) * old_map_width + (x + old_x_offset)]; } } for (int y = 0; y < to_fill_height; y++) { for (int x = 0; x < to_fill_width; x++) { map->tiles[(y + to_fill_y_offset) * map->width + (x + to_fill_x_offset)] = 1; } } player.pos_x = clamp(0, player.pos_x, map->width - 1); player.pos_y = clamp(0, player.pos_y, map->height - 1); map->gpu_buffer = create_buffer(WGPUBufferUsage_Vertex, map->width * map->height * 4, map->tiles, "world_buffer"); if (!map->gpu_buffer) { log_error("Failed to create buffer. Exiting."); exit(1); } free(old_map); wgpuBufferRelease(old_gpu_buffer); } static WGPUTexture create_shader_texture(const char *name, const char *data, uint32_t width, uint32_t height, int channels) { WGPUTextureDescriptor descriptor = { .label = { .data = name, .length = WGPU_STRLEN }, .usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst, .dimension = WGPUTextureDimension_2D, .size = { .width = width, .height = height, .depthOrArrayLayers = 1 }, .format = channels == 4 ? WGPUTextureFormat_RGBA8UnormSrgb : WGPUTextureFormat_R8Unorm, .mipLevelCount = 1, .sampleCount = 1, .viewFormatCount = 0, .viewFormats = NULL, }; WGPUTexture texture = wgpuDeviceCreateTexture(device, &descriptor); if (!texture) { log_error("Failed to create texture."); return NULL; } if (data) { WGPUTexelCopyTextureInfo destination = { .texture = texture, .mipLevel = 0, .origin = { .x = 0, .y = 0, .z = 0 }, .aspect = WGPUTextureAspect_All, }; WGPUTexelCopyBufferLayout data_layout = { .offset = 0, .bytesPerRow = width * channels, .rowsPerImage = height, }; WGPUExtent3D extent = { .width = width, .height = height, .depthOrArrayLayers = 1, }; wgpuQueueWriteTexture(queue, &destination, data, width * height * channels, &data_layout, &extent); } return texture; } static WGPUTexture create_shader_texture(const char *path) { char path_to_load[256] = ASSETS_PATH; SDL_strlcat(path_to_load, path, SDL_arraysize(path_to_load)); int width = 0, height = 0, channels = 0; stbi_uc *data = stbi_load(path_to_load, &width, &height, &channels, 0); if (!data) { log_error("Failed to load texture (\"%s\").", path_to_load); return NULL; } WGPUTexture result = create_shader_texture(path, (char *)data, width, height, channels); if (!result) { log_error("Failed to load texture (\"%s\").", path_to_load); stbi_image_free(data); return NULL; } stbi_image_free(data); return result; } static void blit(char *dst, Sint32 dst_pitch, Sint32 dst_x, Sint32 dst_y, char *src, Sint32 src_pitch, Sint32 width, Sint32 height, int components = 4) { for (Sint32 y = 0; y < height; y++) 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) { const ImGuiContext *context = ImGui::GetCurrentContext(); const ImVec2 padding = context->Style.FramePadding; bool pressed = ImGui::Selectable(label, selected, 0, image_size + padding * 2.0f); ImVec2 min = ImGui::GetItemRectMin(); ImVec2 max = ImGui::GetItemRectMax(); 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; default: SDL_assert_always(false); break; } 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; float blue = linear.z <= 0.0031308f ? 12.92f * linear.z : 1.055f * powf(linear.z, 1.0f / 2.4f) - 0.055; return ImVec4(red, green, blue, linear.w); } static ImVec4 sRGB_to_linear(ImVec4 linear) { float red = linear.x <= 0.0031308f ? linear.x / 12.92f : powf((linear.x + 0.055) / 1.055, 2.4f); float green = linear.y <= 0.0031308f ? linear.y / 12.92f : powf((linear.y + 0.055) / 1.055, 2.4f); float blue = linear.z <= 0.0031308f ? linear.z / 12.92f : powf((linear.z + 0.055) / 1.055, 2.4f); return ImVec4(red, green, blue, linear.w); } static V3 Unproject(V3 screen_pos) { V4 result = inverse_view_matrix * inverse_projection_matrix * V4_(screen_pos, 1.0f); result.xyz /= result.w; return result.xyz; } static V2 get_floor_intersection_of_mouse(V2 mouse_pos) { V2 mouse = remap(V2{ 0, 0 }, V2{ (float)window_width, (float)window_height }, V2{ -1, 1 }, V2{ 1, -1 }, V2{ mouse_pos.x, mouse_pos.y }); V3 camera_position = (inverse_view_matrix * V4_(0, 0, 0, 1)).xyz; V3 probe = Unproject(V3_(mouse, .5)); V3 ray_dir = normalize(probe - camera_position); float t = -camera_position.z / ray_dir.z; V3 floor_intersection = camera_position + (t * ray_dir); return floor_intersection.xy; } #ifdef TRACY_ENABLE static SDL_malloc_func sdl_malloc = NULL; static SDL_calloc_func sdl_calloc = NULL; static SDL_realloc_func sdl_realloc = NULL; static SDL_free_func sdl_free = NULL; static void setup_memory_functions() { SDL_GetMemoryFunctions(&sdl_malloc, &sdl_calloc, &sdl_realloc, &sdl_free); SDL_SetMemoryFunctions( [](size_t size) -> void * { void *result = sdl_malloc(size); TracyAllocN(result, size, "SDL"); return result; }, [](size_t nmemb, size_t size) -> void * { void *result = sdl_calloc(nmemb, size); TracyAllocN(result, nmemb * size, "SDL"); return result; }, [](void *mem, size_t size) -> void * { void *result = sdl_realloc(mem, size); TracyFreeN(mem, "SDL"); TracyAllocN(result, size, "SDL"); return result; }, [](void *mem) { TracyFreeN(mem, "SDL"); sdl_free(mem); } ); } #else static void setup_memory_functions() {} #endif static bool recreate_graphics_pipelines() { WGPUBlendState blend_state = { .color = { .operation = WGPUBlendOperation_Add, .srcFactor = WGPUBlendFactor_SrcAlpha, .dstFactor = WGPUBlendFactor_OneMinusSrcAlpha }, .alpha = { .operation = WGPUBlendOperation_Add, .srcFactor = WGPUBlendFactor_SrcAlpha, .dstFactor = WGPUBlendFactor_OneMinusSrcAlpha }, }; WGPUColorTargetState color_target_state = { .format = WGPUTextureFormat_BGRA8UnormSrgb, .blend = &blend_state, .writeMask = WGPUColorWriteMask_All, }; WGPUBindGroupLayoutEntry frame_data_bind_group_layout_entries[] = { { .binding = 0, .visibility = WGPUShaderStage_Vertex, .buffer = { .type = WGPUBufferBindingType_Uniform, .hasDynamicOffset = false, .minBindingSize = 0, }, }, { .binding = 1, .visibility = WGPUShaderStage_Vertex, .buffer = { .type = WGPUBufferBindingType_Uniform, .hasDynamicOffset = false, .minBindingSize = 0, }, }, }; WGPUBindGroupLayoutDescriptor frame_data_bind_group_layout_descriptor = { .label = { .data = "frame_data_bind_group_layout", .length = WGPU_STRLEN }, .entryCount = SDL_arraysize(frame_data_bind_group_layout_entries), .entries = frame_data_bind_group_layout_entries, }; WGPUBindGroupLayout frame_data_bind_group_layout = wgpuDeviceCreateBindGroupLayout(device, &frame_data_bind_group_layout_descriptor); { // basic_render_pipeline WGPUBindGroupLayoutEntry basic_bind_group_layout_entries[] = { { .binding = 0, .visibility = WGPUShaderStage_Fragment, .texture = { .sampleType = WGPUTextureSampleType_Float, .viewDimension = WGPUTextureViewDimension_2D, .multisampled = false, }, }, { .binding = 1, .visibility = WGPUShaderStage_Fragment, .sampler = { .type = WGPUSamplerBindingType_Filtering, }, }, { .binding = 2, .visibility = WGPUShaderStage_Fragment, .buffer = { .type = WGPUBufferBindingType_Uniform, .hasDynamicOffset = false, .minBindingSize = 0, }, }, }; WGPUBindGroupLayoutDescriptor basic_bind_group_layout_descriptor = { .label = { .data = "basic_bind_group_layout", .length = WGPU_STRLEN }, .entryCount = SDL_arraysize(basic_bind_group_layout_entries), .entries = basic_bind_group_layout_entries, }; WGPUBindGroupLayout basic_bind_group_layout = wgpuDeviceCreateBindGroupLayout(device, &basic_bind_group_layout_descriptor); WGPUBindGroupLayout basic_bind_group_layouts[] = { frame_data_bind_group_layout, basic_bind_group_layout, }; WGPUPipelineLayoutDescriptor basic_pipeline_layout_descriptor = { .label = { .data = "basic_pipeline_layout", .length = WGPU_STRLEN }, .bindGroupLayoutCount = SDL_arraysize(basic_bind_group_layouts), .bindGroupLayouts = basic_bind_group_layouts, }; WGPUPipelineLayout basic_pipeline_layout = wgpuDeviceCreatePipelineLayout(device, &basic_pipeline_layout_descriptor); WGPUShaderSourceWGSL basic_shader_source = { .chain = { .next = NULL, .sType = WGPUSType_ShaderSourceWGSL }, .code = { .data = WGSL_basic, .length = WGSL_basic_num_bytes }, }; WGPUShaderModuleDescriptor basic_shader_descriptor = { .nextInChain = &basic_shader_source.chain, .label = { .data = "basic_shader module", .length = WGPU_STRLEN }, }; WGPUShaderModule basic_shader = wgpuDeviceCreateShaderModule(device, &basic_shader_descriptor); WGPUVertexAttribute vertex_buffer_attributes[] = { { .format = WGPUVertexFormat_Float32x3, .offset = offsetof(Vertex, pos), .shaderLocation = 0, }, { .format = WGPUVertexFormat_Float32x2, .offset = offsetof(Vertex, uv), .shaderLocation = 1, }, }; WGPUVertexAttribute instance_buffer_attributes[] = { { .format = WGPUVertexFormat_Float32x2, .offset = offsetof(Instance, pos), .shaderLocation = 2, }, }; WGPUVertexBufferLayout vertex_buffer_layouts[] = { { .stepMode = WGPUVertexStepMode_Vertex, .arrayStride = sizeof(Vertex), .attributeCount = SDL_arraysize(vertex_buffer_attributes), .attributes = vertex_buffer_attributes, }, { .stepMode = WGPUVertexStepMode_Instance, .arrayStride = sizeof(Instance), .attributeCount = SDL_arraysize(instance_buffer_attributes), .attributes = instance_buffer_attributes, }, }; WGPUFragmentState basic_fragment_state = { .module = basic_shader, .entryPoint = { .data = "main_fragment", .length = WGPU_STRLEN }, .constantCount = 0, .constants = NULL, .targetCount = 1, .targets = &color_target_state, }; WGPURenderPipelineDescriptor basic_render_pipeline_descriptor = { .label = { .data = "basic_render_pipeline", .length = WGPU_STRLEN }, .layout = basic_pipeline_layout, .vertex = { .module = basic_shader, .entryPoint = { .data = "main_vertex", .length = WGPU_STRLEN }, .constantCount = 0, .constants = NULL, .bufferCount = SDL_arraysize(vertex_buffer_layouts), .buffers = vertex_buffer_layouts, }, .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 = &basic_fragment_state, }; basic_render_pipeline = wgpuDeviceCreateRenderPipeline(device, &basic_render_pipeline_descriptor); wgpuBindGroupLayoutRelease(basic_bind_group_layout); wgpuPipelineLayoutRelease(basic_pipeline_layout); wgpuShaderModuleRelease(basic_shader); } { // world_render_pipeline WGPUBindGroupLayoutEntry world_bind_group_layout_entries[] = { { .binding = 0, .visibility = WGPUShaderStage_Fragment, .texture = { .sampleType = WGPUTextureSampleType_Float, .viewDimension = WGPUTextureViewDimension_2DArray, .multisampled = false, }, }, { .binding = 1, .visibility = WGPUShaderStage_Fragment, .sampler = { .type = WGPUSamplerBindingType_Filtering, }, }, { .binding = 2, .visibility = WGPUShaderStage_Fragment, .buffer = { .type = WGPUBufferBindingType_Uniform, .hasDynamicOffset = false, .minBindingSize = 0, }, }, { .binding = 3, .visibility = WGPUShaderStage_Vertex, .buffer = { .type = WGPUBufferBindingType_ReadOnlyStorage, .hasDynamicOffset = false, .minBindingSize = 0, }, }, }; WGPUBindGroupLayoutDescriptor world_bind_group_layout_descriptor = { .label = { .data = "world_bind_group_layout", .length = WGPU_STRLEN }, .entryCount = SDL_arraysize(world_bind_group_layout_entries), .entries = world_bind_group_layout_entries, }; WGPUBindGroupLayout world_bind_group_layout = wgpuDeviceCreateBindGroupLayout(device, &world_bind_group_layout_descriptor); WGPUBindGroupLayout world_bind_group_layouts[] = { frame_data_bind_group_layout, world_bind_group_layout, }; WGPUPipelineLayoutDescriptor world_pipeline_layout_descriptor = { .label = { .data = "world_pipeline_layout", .length = WGPU_STRLEN }, .bindGroupLayoutCount = SDL_arraysize(world_bind_group_layouts), .bindGroupLayouts = world_bind_group_layouts, }; WGPUPipelineLayout world_pipeline_layout = wgpuDeviceCreatePipelineLayout(device, &world_pipeline_layout_descriptor); WGPUShaderSourceWGSL world_shader_source = { .chain = { .next = NULL, .sType = WGPUSType_ShaderSourceWGSL }, .code = { .data = WGSL_world, .length = WGSL_world_num_bytes }, }; WGPUShaderModuleDescriptor world_shader_descriptor = { .nextInChain = &world_shader_source.chain, .label = { .data = "world_shader module", .length = WGPU_STRLEN }, }; WGPUShaderModule world_shader = wgpuDeviceCreateShaderModule(device, &world_shader_descriptor); WGPUVertexAttribute vertex_buffer_attributes[] = { { .format = WGPUVertexFormat_Float32x3, .offset = offsetof(Vertex, pos), .shaderLocation = 0, }, }; WGPUVertexAttribute instance_buffer_attributes[] = { { .format = WGPUVertexFormat_Uint32, .offset = 0, .shaderLocation = 1, }, }; WGPUVertexBufferLayout vertex_buffer_layouts[] = { { .stepMode = WGPUVertexStepMode_Vertex, .arrayStride = sizeof(Vertex), .attributeCount = SDL_arraysize(vertex_buffer_attributes), .attributes = vertex_buffer_attributes, }, { .stepMode = WGPUVertexStepMode_Instance, .arrayStride = sizeof(Uint32), .attributeCount = SDL_arraysize(instance_buffer_attributes), .attributes = instance_buffer_attributes, }, }; WGPUFragmentState world_fragment_state = { .module = world_shader, .entryPoint = { .data = "main_fragment", .length = WGPU_STRLEN }, .constantCount = 0, .constants = NULL, .targetCount = 1, .targets = &color_target_state, }; WGPURenderPipelineDescriptor world_render_pipeline_descriptor = { .label = { .data = "world_render_pipeline", .length = WGPU_STRLEN }, .layout = world_pipeline_layout, .vertex = { .module = world_shader, .entryPoint = { .data = "main_vertex", .length = WGPU_STRLEN }, .constantCount = 0, .constants = NULL, .bufferCount = SDL_arraysize(vertex_buffer_layouts), .buffers = vertex_buffer_layouts, }, .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 = &world_fragment_state, }; world_render_pipeline = wgpuDeviceCreateRenderPipeline(device, &world_render_pipeline_descriptor); wgpuBindGroupLayoutRelease(world_bind_group_layout); wgpuPipelineLayoutRelease(world_pipeline_layout); wgpuShaderModuleRelease(world_shader); } { // grid_render_pipeline WGPUBindGroupLayout grid_bind_group_layouts[] = { frame_data_bind_group_layout, }; WGPUPipelineLayoutDescriptor grid_pipeline_layout_descriptor = { .label = { .data = "grid_pipeline_layout", .length = WGPU_STRLEN }, .bindGroupLayoutCount = SDL_arraysize(grid_bind_group_layouts), .bindGroupLayouts = grid_bind_group_layouts, }; WGPUPipelineLayout grid_pipeline_layout = wgpuDeviceCreatePipelineLayout(device, &grid_pipeline_layout_descriptor); WGPUShaderSourceWGSL grid_shader_source = { .chain = { .next = NULL, .sType = WGPUSType_ShaderSourceWGSL }, .code = { .data = WGSL_grid, .length = WGSL_grid_num_bytes }, }; WGPUShaderModuleDescriptor grid_shader_descriptor = { .nextInChain = &grid_shader_source.chain, .label = { .data = "grid_shader module", .length = WGPU_STRLEN }, }; WGPUShaderModule grid_shader = wgpuDeviceCreateShaderModule(device, &grid_shader_descriptor); WGPUVertexAttribute vertex_buffer_attributes[] = { { .format = WGPUVertexFormat_Float32x3, .offset = offsetof(Vertex, pos), .shaderLocation = 0, }, }; WGPUVertexAttribute instance_buffer_attributes[] = { }; WGPUVertexBufferLayout vertex_buffer_layouts[] = { { .stepMode = WGPUVertexStepMode_Vertex, .arrayStride = sizeof(Vertex), .attributeCount = SDL_arraysize(vertex_buffer_attributes), .attributes = vertex_buffer_attributes, }, }; WGPUFragmentState grid_fragment_state = { .module = grid_shader, .entryPoint = { .data = "main_fragment", .length = WGPU_STRLEN }, .constantCount = 0, .constants = NULL, .targetCount = 1, .targets = &color_target_state, }; WGPURenderPipelineDescriptor grid_render_pipeline_descriptor = { .label = { .data = "grid_render_pipeline", .length = WGPU_STRLEN }, .layout = grid_pipeline_layout, .vertex = { .module = grid_shader, .entryPoint = { .data = "main_vertex", .length = WGPU_STRLEN }, .constantCount = 0, .constants = NULL, .bufferCount = SDL_arraysize(vertex_buffer_layouts), .buffers = vertex_buffer_layouts, }, .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 = &grid_fragment_state, }; grid_render_pipeline = wgpuDeviceCreateRenderPipeline(device, &grid_render_pipeline_descriptor); wgpuPipelineLayoutRelease(grid_pipeline_layout); wgpuShaderModuleRelease(grid_shader); } wgpuBindGroupLayoutRelease(frame_data_bind_group_layout); return true; } static bool recreate_tile_textures() { WGPUTextureFormat view_formats[] = { WGPUTextureFormat_RGBA8Unorm, }; WGPUTextureDescriptor descriptor = { .label = { .data = "tile_textures_array", .length = WGPU_STRLEN }, .usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst, .dimension = WGPUTextureDimension_2D, .size = { .width = TILE_SIZE, .height = TILE_SIZE, .depthOrArrayLayers = SDL_arraysize(tile_infos) }, .format = WGPUTextureFormat_RGBA8UnormSrgb, .mipLevelCount = 1, .sampleCount = 1, .viewFormatCount = SDL_arraysize(view_formats), .viewFormats = view_formats, }; tile_textures_array = wgpuDeviceCreateTexture(device, &descriptor); if (!tile_textures_array) { log_error("Failed to create texture."); return WGPUOptionalBool_False; } tile_textures_array_view = wgpuTextureCreateView(tile_textures_array, 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 = { .format = WGPUTextureFormat_RGBA8Unorm, .dimension = WGPUTextureViewDimension_2D, .mipLevelCount = 1, .baseArrayLayer = i, .arrayLayerCount = 1, }; tile_textures_array_view_individual[i] = wgpuTextureCreateView(tile_textures_array, &descriptor); } for (Uint32 i = 0; i < SDL_arraysize(tile_infos); i++) { char path[256] = ASSETS_PATH; SDL_strlcat(path, tile_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); wgpuTextureRelease(tile_textures_array); return false; } SDL_assert_always(width == TILE_SIZE); SDL_assert_always(height == TILE_SIZE); cpu_tile_infos_buffer[i] = V4_(0, 0, 1, 1); WGPUTexelCopyTextureInfo texel_copy_texture_info = { .texture = tile_textures_array, .mipLevel = 0, .origin = { .x = 0, .y = 0, .z = i }, .aspect = WGPUTextureAspect_All, }; WGPUTexelCopyBufferLayout texel_copy_buffer_layout = { .offset = 0, .bytesPerRow = TILE_SIZE * 4, .rowsPerImage = TILE_SIZE, }; WGPUExtent3D extent = { .width = TILE_SIZE, .height = TILE_SIZE, .depthOrArrayLayers = 1, }; wgpuQueueWriteTexture(queue, &texel_copy_texture_info, data, width * height * 4, &texel_copy_buffer_layout, &extent); stbi_image_free(data); } return true; } static int real_mod(int a, int b) { int result = a % b; return result >= 0 ? result : result + b; } static bool imgui_time_picker(const char *label, int time[3]) { bool result = ImGui::DragScalarN(label, ImGuiDataType_S32, time, 3); time[1] += time[2] >= 0 ? time[2] / 60 : -1; time[0] += time[1] >= 0 ? time[1] / 60 : -1; time[2] = real_mod(time[2], 60); time[1] = real_mod(time[1], 60); time[0] = real_mod(time[0], 24); return result; } static Uint32 get_corner_info(Sint32 tile_pos_x, Sint32 tile_pos_y) { if (tile_pos_x < 0 || tile_pos_x >= current_map.width || tile_pos_y < 0 || tile_pos_y >= current_map.height) return 0; Uint32 tile = current_map.tiles[tile_pos_y * current_map.width + tile_pos_x]; Uint32 index = tile & 0xffff; Uint32 rotation = (tile & 0x30000) >> 16; Uint32 base_corner_info = tile_infos[index].corner_info; switch (rotation) { case 0: return base_corner_info; case 1: return std::rotl(base_corner_info, 8); case 2: return std::rotl(base_corner_info, 16); case 3: return std::rotl(base_corner_info, 24); default: return tile_infos[0].corner_info; } } static Uint32 find_matching_tile(Uint32 corner_info) { Uint32 corner_info_1 = std::rotr(corner_info, 8); Uint32 corner_info_2 = std::rotr(corner_info, 16); Uint32 corner_info_3 = std::rotr(corner_info, 24); for (Uint32 i = 0; i < SDL_arraysize(tile_infos); i++) { if (corner_info == tile_infos[i].corner_info) return i; if (corner_info_1 == tile_infos[i].corner_info) return (1 << 16) | i; if (corner_info_2 == tile_infos[i].corner_info) return (2 << 16) | i; if (corner_info_3 == tile_infos[i].corner_info) return (3 << 16) | i; } return 0; } static void change_map_tile(Sint32 pos_x, Sint32 pos_y, TileKind kind) { __m128i corner_infos = _mm_setr_epi32(get_corner_info(pos_x, pos_y + 1), get_corner_info(pos_x + 1, pos_y + 1), get_corner_info(pos_x + 1, pos_y), get_corner_info(pos_x, pos_y)); __m128i none_mask = _mm_cmpeq_epi8(corner_infos, _mm_set1_epi8(TILEKIND_NONE)); __m128i error_mask = _mm_cmpeq_epi8(corner_infos, _mm_set1_epi8(TILEKIND_ERROR)); __m128i kind_mask = _mm_setr_epi32(0x0000ff00, 0x000000ff, 0xff000000, 0x00ff0000); __m128i replace_mask = _mm_or_si128(_mm_or_si128(none_mask, kind_mask), error_mask); corner_infos = _mm_andnot_si128(replace_mask, corner_infos); corner_infos = _mm_or_si128(corner_infos, _mm_and_si128(replace_mask, _mm_set1_epi8(kind))); Uint32 corner_infos_u32[4]; _mm_storeu_si128((__m128i *)&corner_infos_u32, corner_infos); if (0 <= pos_x + 0 && pos_x + 0 < current_map.width && 0 <= pos_y + 1 && pos_y + 1 < current_map.height) current_map.tiles[(pos_y + 1) * current_map.width + pos_x + 0] = find_matching_tile(corner_infos_u32[0]); if (0 <= pos_x + 1 && pos_x + 1 < current_map.width && 0 <= pos_y + 1 && pos_y + 1 < current_map.height) current_map.tiles[(pos_y + 1) * current_map.width + pos_x + 1] = find_matching_tile(corner_infos_u32[1]); if (0 <= pos_x + 1 && pos_x + 1 < current_map.width && 0 <= pos_y + 0 && pos_y + 0 < current_map.height) current_map.tiles[(pos_y + 0) * current_map.width + pos_x + 1] = find_matching_tile(corner_infos_u32[2]); if (0 <= pos_x + 0 && pos_x + 0 < current_map.width && 0 <= pos_y + 0 && pos_y + 0 < current_map.height) current_map.tiles[(pos_y + 0) * current_map.width + pos_x + 0] = find_matching_tile(corner_infos_u32[3]); update_buffer(current_map.gpu_buffer, 0, current_map.width * current_map.height * sizeof(Uint32), current_map.tiles); } static void SameLineOrWrap(const ImVec2& size) { ImGuiWindow *window = ImGui::GetCurrentWindow(); ImVec2 pos = ImVec2(window->DC.CursorPosPrevLine.x + ImGui::GetStyle().ItemSpacing.x, window->DC.CursorPosPrevLine.y); if (window->WorkRect.Contains(ImRect(pos, pos + size))) ImGui::SameLine(); } static Sint32x2 grid_tile_pos_from_floor_intersection(V2 floor_intersection) { return { (Sint32)SDL_floorf(floor_intersection.x + (selected_tile == -1 ? 0.5f : 1.0f)), (Sint32)SDL_floorf(floor_intersection.y + (selected_tile == -1 ? 0.5f : 1.0f)), }; } void request_device_callback(WGPURequestDeviceStatus status, WGPUDevice device_, WGPUStringView message, void *userdata1, void *userdata2) { if (status != WGPURequestDeviceStatus_Success) { log_error("Failed to request webgpu device."); wgpu_init_done = true; return; } device = device_; queue = wgpuDeviceGetQueue(device); wgpu_init_done = true; } void adapter_request_callback(WGPURequestAdapterStatus status, WGPUAdapter adapter, WGPUStringView message, void *userdata1, void *userdata2) { if (status != WGPURequestAdapterStatus_Success) { log_error("Failed to request webgpu adapter."); wgpu_init_done = true; return; } WGPURequestDeviceCallbackInfo request_device_callback_info = { .mode = WGPUCallbackMode_AllowProcessEvents, .callback = request_device_callback, }; WGPUDeviceDescriptor device_descriptor = { .label = {}, .requiredFeatureCount = 0, .requiredFeatures = NULL, .requiredLimits = NULL, .defaultQueue = {}, .deviceLostCallbackInfo = {}, .uncapturedErrorCallbackInfo = {}, }; wgpuAdapterRequestDevice(adapter, &device_descriptor, request_device_callback_info); } WGPUSurface create_wgpu_surface_for_SDL_window(SDL_Window *window) { SDL_PropertiesID properties = SDL_GetWindowProperties(window); #if defined(SDL_PLATFORM_LINUX) const char *display_system = SDL_GetCurrentVideoDriver(); if (SDL_strcmp(display_system, "wayland") == 0) { void *display = SDL_GetPointerProperty(properties, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, NULL); void *surface = SDL_GetPointerProperty(properties, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, NULL); if (!display || !surface) return NULL; WGPUSurfaceSourceWaylandSurface surface_source = { .chain = { .next = NULL, .sType = WGPUSType_SurfaceSourceWaylandSurface }, .display = display, .surface = surface, }; WGPUSurfaceDescriptor descriptor = { .nextInChain = &surface_source.chain, .label = { .data = NULL, .length = WGPU_STRLEN }, }; return wgpuInstanceCreateSurface(instance, &descriptor); } else if (SDL_strcmp(display_system, "x11") == 0) { void *display = SDL_GetPointerProperty(properties, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, NULL); uint64_t xlib_window = SDL_GetNumberProperty (properties, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, NULL); if (!display || !xlib_window) return NULL; WGPUSurfaceSourceXlibWindow surface_source = { .chain = { .next = NULL, .sType = WGPUSType_SurfaceSourceXlibWindow }, .display = display, .window = xlib_window, }; WGPUSurfaceDescriptor descriptor = { .nextInChain = &surface_source.chain, .label = { .data = NULL, .length = WGPU_STRLEN }, }; return wgpuInstanceCreateSurface(instance, &descriptor); } else { log_error("create_wgpu_surface_for_SDL_window is not implemented for this display system (%s).", display_system); return NULL; } #elif defined(SDL_PLATFORM_WINDOWS) void *hinstance = SDL_GetPointerProperty(properties, SDL_PROP_WINDOW_WIN32_INSTANCE_POINTER, NULL); void *hwnd = SDL_GetPointerProperty(properties, SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); if (!hinstance || !hwnd) return NULL; WGPUSurfaceSourceWindowsHWND surface_source = { .chain = { .next = NULL, .sType = WGPUSType_SurfaceSourceWindowsHWND }, .hinstance = hinstance, .hwnd = hwnd, }; WGPUSurfaceDescriptor descriptor = { .nextInChain = &surface_source.chain, .label = { .data = NULL, .length = WGPU_STRLEN }, }; return wgpuInstanceCreateSurface(instance, &descriptor); #else static_assert(false, "create_wgpu_surface_for_SDL_window is not implemented for this platform."); #endif return NULL; } int main(int argc, char **argv) { setup_memory_functions(); #ifdef SDL_PLATFORM_LINUX if (getenv("ENABLE_VULKAN_RENDERDOC_CAPTURE")) SDL_SetHint(SDL_HINT_VIDEO_DRIVER, "x11,wayland"); #endif if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS | SDL_INIT_GAMEPAD)) { log_error("Failed to initialize SDL (%s). Exiting.", SDL_GetError()); return 1; } if (!Mix_Init(MIX_INIT_OPUS)) { log_error("Failed to init SDL_mixer. Exiting."); return 1; } instance = wgpuCreateInstance(NULL); if (!instance) { log_error("Failed to create webgpu instance. Exiting."); return 1; } window = SDL_CreateWindow("Mikemon", 1280, 720, SDL_WINDOW_RESIZABLE); if (!window) { log_error("Failed to create window (%s). Exiting.", SDL_GetError()); return 1; } surface = create_wgpu_surface_for_SDL_window(window); if (!surface) { log_error("Failed to create webgpu surface for SDL window. Exiting."); return 1; } WGPURequestAdapterCallbackInfo request_adapter_callback_info = { .mode = WGPUCallbackMode_AllowProcessEvents, .callback = adapter_request_callback, }; WGPURequestAdapterOptions request_adapter_options = { .featureLevel = WGPUFeatureLevel_Core, .powerPreference = WGPUPowerPreference_HighPerformance, .forceFallbackAdapter = false, .backendType = WGPUBackendType_Vulkan, .compatibleSurface = surface, }; wgpuInstanceRequestAdapter(instance, &request_adapter_options, request_adapter_callback_info); while (!wgpu_init_done) { wgpuInstanceProcessEvents(instance); } if (!device) { log_error("Failed to initialize webgpu. Exiting."); return 1; } WGPUSurfaceConfiguration surface_configuration = { .device = device, .format = WGPUTextureFormat_BGRA8UnormSrgb, .usage = WGPUTextureUsage_RenderAttachment, .width = 1280, .height = 720, .viewFormatCount = 0, .viewFormats = NULL, .alphaMode = WGPUCompositeAlphaMode_Opaque, .presentMode = WGPUPresentMode_Fifo, }; wgpuSurfaceConfigure(surface, &surface_configuration); if (!recreate_graphics_pipelines()) { log_error("Failed to create graphics pipelines. Exiting."); return 1; } if (!load_map("map.sv", ¤t_map)) { log_error("Failed to load initial map. Exiting."); return 1; } WGPUTexture player_texture = create_shader_texture("decorations/strawberry.png"); if (!player_texture) { log_error("Failed to create shader texture. Exiting."); return 1; } WGPUTextureView player_texture_view = wgpuTextureCreateView(player_texture, NULL); if (!recreate_tile_textures()) { log_error("Failed to create tile textures. Exiting."); return 1; } WGPUSamplerDescriptor pixel_sampler_descriptor = { .label = { .data = "pixel_sampler", .length = WGPU_STRLEN }, .addressModeU = WGPUAddressMode_ClampToEdge, .addressModeV = WGPUAddressMode_ClampToEdge, .addressModeW = WGPUAddressMode_ClampToEdge, .magFilter = WGPUFilterMode_Linear, .minFilter = WGPUFilterMode_Linear, .mipmapFilter = WGPUMipmapFilterMode_Linear, .maxAnisotropy = 16, }; pixel_sampler = wgpuDeviceCreateSampler(device, &pixel_sampler_descriptor); view_projection_matrix_buffer = create_buffer(WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst, sizeof(M4x4), NULL, "view_projection_matrix_buffer"); if (!view_projection_matrix_buffer) { log_error("Failed to create buffer. Exiting."); return 1; } per_frame_buffer = create_buffer(WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst, sizeof(per_frame), NULL, "per_frame_buffer"); if (!per_frame_buffer) { log_error("Failed to create buffer. Exiting."); return 1; } tint_color_buffer = create_buffer(WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst, sizeof(V3), NULL, "tint_color_buffer"); if (!tint_color_buffer) { log_error("Failed to create buffer. Exiting."); return 1; } vertex_buffer = create_buffer(WGPUBufferUsage_Vertex, sizeof(vertices), vertices, "vertex_buffer"); if (!vertex_buffer) { log_error("Failed to create buffer. Exiting."); return 1; } index_buffer = create_buffer(WGPUBufferUsage_Index, sizeof(indices), indices, "index_buffer"); if (!index_buffer) { log_error("Failed to create buffer. Exiting."); return 1; } grid_vertex_buffer = create_buffer(WGPUBufferUsage_Vertex, sizeof(grid_vertices), grid_vertices, "grid_vertex_buffer"); if (!grid_vertex_buffer) { log_error("Failed to create buffer. Exiting."); return 1; } grid_index_buffer = create_buffer(WGPUBufferUsage_Index, sizeof(grid_indices), grid_indices, "grid_index_buffer"); if (!grid_index_buffer) { log_error("Failed to create buffer. Exiting."); return 1; } player_instance_buffer = create_buffer(WGPUBufferUsage_Vertex | WGPUBufferUsage_CopyDst, sizeof(player_instance), &player_instance, "player_instance_buffer"); if (!player_instance_buffer) { log_error("Failed to create buffer. Exiting."); return 1; } tile_infos_buffer = create_buffer(WGPUBufferUsage_Storage, SDL_arraysize(cpu_tile_infos_buffer) * sizeof(*cpu_tile_infos_buffer), cpu_tile_infos_buffer, "tile_infos_buffer"); if (!tile_infos_buffer) { log_error("Failed to create buffer. Exiting."); return 1; } WGPUBindGroupEntry per_frame_bind_group_entries[] = { { .binding = 0, .buffer = view_projection_matrix_buffer, .offset = 0, .size = WGPU_WHOLE_SIZE }, { .binding = 1, .buffer = per_frame_buffer, .offset = 0, .size = WGPU_WHOLE_SIZE }, }; WGPUBindGroupDescriptor per_frame_bind_group_descriptor = { .label = { .data = "per_frame_bind_group", .length = WGPU_STRLEN }, .layout = wgpuRenderPipelineGetBindGroupLayout(world_render_pipeline, 0), .entryCount = SDL_arraysize(per_frame_bind_group_entries), .entries = per_frame_bind_group_entries, }; per_frame_bind_group = wgpuDeviceCreateBindGroup(device, &per_frame_bind_group_descriptor); WGPUBindGroupEntry world_bind_group_entries[] = { { .binding = 0, .textureView = tile_textures_array_view }, { .binding = 1, .sampler = pixel_sampler }, { .binding = 2, .buffer = tint_color_buffer, .offset = 0, .size = WGPU_WHOLE_SIZE }, { .binding = 3, .buffer = tile_infos_buffer, .offset = 0, .size = WGPU_WHOLE_SIZE }, }; WGPUBindGroupDescriptor world_bind_group_descriptor = { .label = { .data = "world_bind_group", .length = WGPU_STRLEN }, .layout = wgpuRenderPipelineGetBindGroupLayout(world_render_pipeline, 1), .entryCount = SDL_arraysize(world_bind_group_entries), .entries = world_bind_group_entries, }; world_bind_group = wgpuDeviceCreateBindGroup(device, &world_bind_group_descriptor); WGPUBindGroupEntry basic_bind_group_entries[] = { { .binding = 0, .textureView = player_texture_view }, { .binding = 1, .sampler = pixel_sampler }, { .binding = 2, .buffer = tint_color_buffer, .offset = 0, .size = WGPU_WHOLE_SIZE }, }; WGPUBindGroupDescriptor basic_bind_group_descriptor = { .label = { .data = "basic_bind_group", .length = WGPU_STRLEN }, .layout = wgpuRenderPipelineGetBindGroupLayout(basic_render_pipeline, 1), .entryCount = SDL_arraysize(basic_bind_group_entries), .entries = basic_bind_group_entries, }; basic_bind_group = wgpuDeviceCreateBindGroup(device, &basic_bind_group_descriptor); SDL_AudioSpec audio_spec = { .format = SDL_AUDIO_F32, .channels = 2, .freq = 48000, }; if (!Mix_OpenAudio(0, &audio_spec)) { log_error("Failed to open default audio device. Ignoring."); } music_setting_off_piano = Mix_LoadMUS(ASSETS_PATH "music/setting_off_piano.opus"); if (!music_setting_off_piano) { log_error("Failed to load music setting_off_piano.opus. Ignoring."); } IMGUI_CHECKVERSION(); ImGuiContext *imgui_context = ImGui::CreateContext(); ImGuiIO &io = ImGui::GetIO(); io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; ImGui::StyleColorsDark(); ImGui_ImplSDL3_InitForOther(window); ImGui_ImplWGPU_InitInfo imgui_init_info = {}; imgui_init_info.Device = device; imgui_init_info.RenderTargetFormat = WGPUTextureFormat_BGRA8UnormSrgb; imgui_init_info.PipelineMultisampleState = { .count = 4, .mask = ~0u, .alphaToCoverageEnabled = false, }; ImGui_ImplWGPU_Init(&imgui_init_info); ImGuiSettingsHandler time_tints_settings_handler = {}; time_tints_settings_handler.TypeName = "TimeTints"; time_tints_settings_handler.TypeHash = ImHashStr("TimeTints"); time_tints_settings_handler.ReadOpenFn = [](ImGuiContext *context, ImGuiSettingsHandler *handler, const char *name) -> void * { if (strcmp(name, "num") == 0) return (void *)-1; int num = atoi(name) + 1; return (void *)(Sint64)num; }; time_tints_settings_handler.ReadLineFn = [](ImGuiContext *context, ImGuiSettingsHandler *handler, void *entry, const char *line) { if (entry == (void *)-1) { SDL_sscanf(line, "num=%d", &num_used_tint_times); return; } if (strncmp(line, "time", 4) == 0) { SDL_sscanf(line, "time=%d %d %d", &time_tints_times[(size_t)entry - 1][0], &time_tints_times[(size_t)entry - 1][1], &time_tints_times[(size_t)entry - 1][2]); } else if(strncmp(line, "color", 5) == 0) { SDL_sscanf(line, "color=%g %g %g", &time_tints[(size_t)entry - 1][0], &time_tints[(size_t)entry - 1][1], &time_tints[(size_t)entry - 1][2]); } }; time_tints_settings_handler.WriteAllFn = [](ImGuiContext *context, ImGuiSettingsHandler *handler, ImGuiTextBuffer *buffer) { buffer->append("[TimeTints][num]\n"); buffer->appendf("num=%d\n\n", num_used_tint_times); for (int i = 0; i < num_used_tint_times; i++) { buffer->appendf("[TimeTints][%d]\n", i); buffer->appendf("time=%d %d %d\n", time_tints_times[i][0], time_tints_times[i][1], time_tints_times[i][2]); buffer->appendf("color=%g %g %g\n\n", time_tints[i][0], time_tints[i][1], time_tints[i][2]); } }; ImGui::AddSettingsHandler(&time_tints_settings_handler); ImGuiSettingsHandler settings_handler = {}; settings_handler.TypeName = "Settings"; settings_handler.TypeHash = ImHashStr("Settings"); settings_handler.ReadOpenFn = [](ImGuiContext *context, ImGuiSettingsHandler *handler, const char *name) -> void * { if (strcmp(name, "Audio") == 0) return (void *)SETTINGS_AUDIO; return (void *)SETTINGS_UNKNOWN; }; settings_handler.ReadLineFn = [](ImGuiContext *context, ImGuiSettingsHandler *handler, void *entry, const char *line) { if (entry == (void *)SETTINGS_AUDIO) { if (SDL_sscanf(line, "Master=%f", &volume_master) == 1) { Mix_MasterVolume(MIX_MAX_VOLUME * (volume_master / 100.0f) * (volume_sfx / 100.0f)); Mix_VolumeMusic (MIX_MAX_VOLUME * (volume_master / 100.0f) * (volume_music / 100.0f)); } if (SDL_sscanf(line, "Music=%f", &volume_music) == 1) Mix_VolumeMusic(MIX_MAX_VOLUME * (volume_master / 100.0f) * (volume_music / 100.0f)); if (SDL_sscanf(line, "SFX=%f", &volume_sfx) == 1) Mix_MasterVolume(MIX_MAX_VOLUME * (volume_master / 100.0f) * (volume_sfx / 100.0f)); return; } }; settings_handler.WriteAllFn = [](ImGuiContext *context, ImGuiSettingsHandler *handler, ImGuiTextBuffer *buffer) { buffer->append("[Settings][Audio]\n"); buffer->appendf("Master=%.0f\n", volume_master); buffer->appendf("Music=%.0f\n", volume_music); buffer->appendf("SFX=%.0f\n", volume_sfx); buffer->append("\n"); }; ImGui::AddSettingsHandler(&settings_handler); bool first_frame = true; bool show_demo_window = false; bool show_tile_picker = false; bool show_settings = false; SDL_GetWindowSizeInPixels(window, &window_width, &window_height); Uint32 framebuffer_width = 0; Uint32 framebuffer_height = 0; // MSG Message; while (Running) { ZoneScopedN("Loop"); ImGui_ImplWGPU_NewFrame(); ImGui_ImplSDL3_NewFrame(); ImGui::NewFrame(); SDL_GetCurrentTime(&time); if (use_actual_time) SDL_TimeToDateTime(time, &calendar_time, true); calendar_time.minute += calendar_time.second >= 0 ? calendar_time.second / 60 : calendar_time.second / 60 - 1; calendar_time.hour += calendar_time.minute >= 0 ? calendar_time.minute / 60 : calendar_time.minute / 60 - 1; calendar_time.second = real_mod(calendar_time.second, 60); calendar_time.minute = real_mod(calendar_time.minute, 60); calendar_time.hour = real_mod(calendar_time.hour, 24); if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("File")) { ImGui::MenuItem("Settings", NULL, &show_settings); ImGui::MenuItem("Demo Window", NULL, &show_demo_window); ImGui::Separator(); if (ImGui::MenuItem("Exit")) { Running = false; } ImGui::EndMenu(); } if (ImGui::BeginMenu("Edit")) { ImGui::MenuItem("Tile Picker", NULL, &show_tile_picker); ImGui::EndMenu(); } ImGui::EndMainMenuBar(); } ImGuiID main_viewport_dock = ImGui::GetID("main_viewport_dock"); if (!ImGui::DockBuilderGetNode(main_viewport_dock)) { ImGui::DockBuilderAddNode (main_viewport_dock, (ImGuiDockNodeFlags)ImGuiDockNodeFlags_DockSpace | ImGuiDockNodeFlags_AutoHideTabBar | ImGuiDockNodeFlags_PassthruCentralNode | ImGuiDockNodeFlags_NoDockingOverCentralNode); ImGui::DockBuilderSetNodePos (main_viewport_dock, ImGui::GetMainViewport()->WorkPos); ImGui::DockBuilderSetNodeSize(main_viewport_dock, ImGui::GetMainViewport()->WorkSize); ImGuiID left_dock = ImGui::DockBuilderSplitNode(main_viewport_dock, ImGuiDir_Left, 0.2f, NULL, NULL); ImGui::DockBuilderDockWindow("Tile Picker", left_dock); ImGui::DockBuilderFinish(main_viewport_dock); } ImGui::DockSpaceOverViewport(main_viewport_dock, ImGui::GetMainViewport(), ImGuiDockNodeFlags_AutoHideTabBar | ImGuiDockNodeFlags_PassthruCentralNode | ImGuiDockNodeFlags_NoDockingOverCentralNode); if (show_settings) { ImGui::SetNextWindowSize(ImVec2(400, 0), ImGuiCond_FirstUseEver); if (ImGui::Begin("Settings", &show_settings)) { if (ImGui::DragFloat("Master", &volume_master, 1.0f, 0.0f, 100.0f, "%.0f", ImGuiSliderFlags_AlwaysClamp)) { Mix_MasterVolume(MIX_MAX_VOLUME * (volume_master / 100.0f) * (volume_sfx / 100.0f)); Mix_VolumeMusic (MIX_MAX_VOLUME * (volume_master / 100.0f) * (volume_music / 100.0f)); }; if (ImGui::DragFloat("Music", &volume_music, 1.0f, 0.0f, 100.0f, "%.0f", ImGuiSliderFlags_AlwaysClamp)) { Mix_VolumeMusic(MIX_MAX_VOLUME * (volume_master / 100.0f) * (volume_music / 100.0f)); } if (ImGui::DragFloat("SFX", &volume_sfx, 1.0f, 0.0f, 100.0f, "%.0f", ImGuiSliderFlags_AlwaysClamp)) { Mix_MasterVolume(MIX_MAX_VOLUME * (volume_master / 100.0f) * (volume_sfx / 100.0f)); } ImGui::NewLine(); ImGui::DragFloat("fovy", &camera_fovy_degrees); ImGui::DragFloat("camera_distance", &camera_distance, 0.25f, 1.0f, INFINITY); ImGui::DragFloat("camera_tilt", &camera_tilt, 0.25f, 0.0f, 89.0f); ImGui::NewLine(); ImGui::BeginDisabled(use_actual_time); ImGui::DragScalarN("Time", ImGuiDataType_S32, &calendar_time.hour, 3); ImGui::EndDisabled(); ImGui::Checkbox("use actual time", &use_actual_time); ImGui::NewLine(); for (int i = 0; i < num_used_tint_times; i++) { ImGui::PushID(i); imgui_time_picker("##time", time_tints_times[i]); ImGui::PopID(); } if (ImGui::Button("Add")) num_used_tint_times = clamp(1, num_used_tint_times + 1, MAX_TINT_TIMES); ImGui::SameLine(); if (ImGui::Button("Remove")) num_used_tint_times = clamp(1, num_used_tint_times - 1, MAX_TINT_TIMES); ImGui::NewLine(); for (int i = 0; i < num_used_tint_times; i++) { ImGui::PushID(i); ImGui::ColorEdit3("##color", time_tints[i].E); ImGui::PopID(); } if (!ImGui::IsAnyItemActive()) { for (int j = 0; j < num_used_tint_times; j++) { for (int i = 0; i < num_used_tint_times - 1; i++) { if (time_tints_times[i][0] > time_tints_times[i + 1][0] || time_tints_times[i][0] == time_tints_times[i + 1][0] && time_tints_times[i][1] > time_tints_times[i + 1][1] || time_tints_times[i][0] == time_tints_times[i + 1][0] && time_tints_times[i][1] == time_tints_times[i + 1][1] && time_tints_times[i][2] > time_tints_times[i + 1][2]) { int temp_time[3] = { time_tints_times[i][0], time_tints_times[i][1], time_tints_times[i][2] }; V3 temp_color = time_tints[i]; time_tints_times[i][0] = time_tints_times[i + 1][0]; time_tints_times[i][1] = time_tints_times[i + 1][1]; time_tints_times[i][2] = time_tints_times[i + 1][2]; time_tints[i] = time_tints[i + 1]; time_tints_times[i + 1][0] = temp_time[0]; time_tints_times[i + 1][1] = temp_time[1]; time_tints_times[i + 1][2] = temp_time[2]; time_tints[i + 1] = temp_color; } } } } } ImGui::End(); } if (show_tile_picker) { if (ImGui::Begin("Tile Picker", &show_tile_picker, ImGuiWindowFlags_NoFocusOnAppearing)) { if (ImGui::Selectable("None", selected_tile_kind == -1 && selected_tile == -1)) { selected_tile_kind = -1; selected_tile = -1; } if (ImGui::Selectable("Grass", selected_tile_kind == TILEKIND_GRASS)) { selected_tile_kind = TILEKIND_GRASS; selected_tile = -1; } if (ImGui::Selectable("Ground", selected_tile_kind == TILEKIND_GROUND)) { selected_tile_kind = TILEKIND_GROUND; selected_tile = -1; } 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))) { selected_tile_kind = -1; selected_tile = i; } ImGui::PopID(); } 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)) selected_rotation = 0; SameLineOrWrap(ImVec2(32, 32)); if (SelectableTile("##90", selected_rotation == 1, selected_tile, ImVec2(32, 32), uv0, uv1, 1)) selected_rotation = 1; SameLineOrWrap(ImVec2(32, 32)); if (SelectableTile("##180", selected_rotation == 2, selected_tile, ImVec2(32, 32), uv0, uv1, 2)) selected_rotation = 2; SameLineOrWrap(ImVec2(32, 32)); if (SelectableTile("##270", selected_rotation == 3, selected_tile, ImVec2(32, 32), uv0, uv1, 3)) selected_rotation = 3; if (ImGui::Selectable("Random", selected_rotation == -1)) selected_rotation = -1; } if (selected_tile != -1 && ImGui::IsWindowFocused() && ImGui::IsKeyPressed(ImGuiKey_R, false)) { if (ImGui::IsKeyDown(ImGuiKey_LeftShift)) { selected_rotation = (selected_rotation - 1) & 3; } else { selected_rotation = (selected_rotation + 1) & 3; } } } ImGui::End(); } else { selected_tile = -1; selected_tile_kind = -1; } if (show_demo_window) ImGui::ShowDemoWindow(&show_demo_window); if (first_frame) { ImGui::SetWindowFocus(NULL); first_frame = false; } TracyCZoneN(tracy_wgpuSurfaceGetCurrentTexture, "wgpuSurfaceGetCurrentTexture", true); WGPUSurfaceTexture surface_texture = {}; wgpuSurfaceGetCurrentTexture(surface, &surface_texture); if (surface_texture.status != WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal && surface_texture.status != WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal) { log_error("Failed to get current surface texture (%x). Exiting.", surface_texture.status); return 1; } WGPUTextureView surface_texture_view = wgpuTextureCreateView(surface_texture.texture, NULL); TracyCZoneEnd(tracy_wgpuSurfaceGetCurrentTexture); { ZoneScopedN("Events"); SDL_Event event; while (SDL_PollEvent(&event)) { ZoneScopedN("Event"); ImGui_ImplSDL3_ProcessEvent(&event); switch (event.type) { case SDL_EVENT_QUIT: { Running = false; } break; case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: { window_width = event.window.data1; window_height = event.window.data2; } break; case SDL_EVENT_KEY_DOWN: { if (io.WantCaptureKeyboard) continue; SDL_Keymod modifiers = SDL_GetModState(); if (event.key.key == SDLK_UP || event.key.key == SDLK_W) { player.pos_y = clamp(0, player.pos_y + 1, current_map.height - 2); } if (event.key.key == SDLK_LEFT || event.key.key == SDLK_A) { player.pos_x = clamp(0, player.pos_x - 1, current_map.width - 2); } if (event.key.key == SDLK_DOWN || event.key.key == SDLK_S) { player.pos_y = clamp(0, player.pos_y - 1, current_map.height - 2); } if (event.key.key == SDLK_RIGHT || event.key.key == SDLK_D) { player.pos_x = clamp(0, player.pos_x + 1, current_map.width - 2); } if (event.key.key == SDLK_F1) { save_map(current_map); } if (event.key.key == SDLK_F4) { char *map_path = SDL_strdup(current_map.name); unload_map(¤t_map); load_map(map_path, ¤t_map); SDL_free(map_path); } if (event.key.key == SDLK_R) { if (selected_tile != -1 && selected_rotation != -1) { if (modifiers & SDL_KMOD_SHIFT) { selected_rotation = (selected_rotation - 1) & 3; } else { selected_rotation = (selected_rotation + 1) & 3; } } } } break; case SDL_EVENT_MOUSE_BUTTON_DOWN: { if (io.WantCaptureMouse) continue; V2 floor_intersection = get_floor_intersection_of_mouse(V2_(event.button.x, event.button.y)); Sint32x2 tile_pos = grid_tile_pos_from_floor_intersection(floor_intersection); drag_start_pos = floor_intersection; if (selected_tile_kind != -1) { change_map_tile(tile_pos.x, tile_pos.y, (TileKind)selected_tile_kind); if (-1 <= tile_pos.x && tile_pos.x < current_map.width && -1 <= tile_pos.y && tile_pos.y < current_map.height) { dragging_tile_change = true; } } if (0 <= tile_pos.x && tile_pos.x < current_map.width && 0 <= tile_pos.y && tile_pos.y < current_map.height) { dragging_tile_change = true; } SDL_Keymod modifiers = SDL_GetModState(); if (modifiers & SDL_KMOD_SHIFT && tile_pos.x <= -1) { if(modifiers & SDL_KMOD_CTRL) change_map_size(¤t_map, 'W', -1); else change_map_size(¤t_map, 'W', 1); } if (modifiers & SDL_KMOD_SHIFT && tile_pos.x == current_map.width) { if (modifiers & SDL_KMOD_CTRL) change_map_size(¤t_map, 'E', -1); else change_map_size(¤t_map, 'E', 1); } if (modifiers & SDL_KMOD_SHIFT && tile_pos.y <= -1) { if (modifiers & SDL_KMOD_CTRL) change_map_size(¤t_map, 'N', -1); else change_map_size(¤t_map, 'N', 1); } if (modifiers & SDL_KMOD_SHIFT && tile_pos.y == current_map.height) { if (modifiers & SDL_KMOD_CTRL) change_map_size(¤t_map, 'S', -1); else change_map_size(¤t_map, 'S', 1); } } break; case SDL_EVENT_MOUSE_BUTTON_UP: { if (io.WantCaptureMouse) continue; if (selected_tile != -1 && dragging_tile_change) { V2 floor_intersection = get_floor_intersection_of_mouse(V2_(event.button.x, event.button.y)); Sint32x2 tile_pos = grid_tile_pos_from_floor_intersection(floor_intersection); Sint32 tile_x = clamp(0, tile_pos.x, current_map.width - 1); Sint32 tile_y = clamp(0, tile_pos.y, current_map.height - 1); Sint32x2 drag_start = grid_tile_pos_from_floor_intersection(drag_start_pos); Sint32 start_x = min(tile_x, drag_start.x); Sint32 start_y = min(tile_y, drag_start.y); Sint32 end_x = max(tile_x, drag_start.x); Sint32 end_y = max(tile_y, drag_start.y); for (Sint32 y = start_y; y <= end_y; y++) { for (Sint32 x = start_x; x <= end_x; x++) { if (selected_rotation == -1) { Sint32 rotation = SDL_rand(4); current_map.tiles[x + current_map.width * y] = ((rotation & 3) << 16) | selected_tile; } else { current_map.tiles[x + current_map.width * y] = ((selected_rotation & 3) << 16) | selected_tile; } } } update_buffer(current_map.gpu_buffer, 0, current_map.width * current_map.height * sizeof(Uint32), current_map.tiles); } dragging_tile_change = false; } break; case SDL_EVENT_MOUSE_MOTION: { mouse_pos = V2_(event.motion.x, event.motion.y); if (selected_tile_kind != -1 && dragging_tile_change) { V2 floor_intersection = get_floor_intersection_of_mouse(mouse_pos); Sint32x2 tile_pos = grid_tile_pos_from_floor_intersection(floor_intersection); change_map_tile(tile_pos.x, tile_pos.y, (TileKind)selected_tile_kind); } } break; case SDL_EVENT_GAMEPAD_BUTTON_DOWN: { if (io.WantCaptureKeyboard) continue; if (event.gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_UP) { player.pos_y = clamp(0, player.pos_y + 1, current_map.height - 2); } if (event.gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_LEFT) { player.pos_x = clamp(0, player.pos_x - 1, current_map.width - 2); } if (event.gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_DOWN) { player.pos_y = clamp(0, player.pos_y - 1, current_map.height - 2); } if (event.gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_RIGHT) { player.pos_x = clamp(0, player.pos_x + 1, current_map.width - 2); } } break; } } } if (!Mix_PlayingMusic()) { Mix_PlayMusic(music_setting_off_piano, -1); } Uint32 surface_width = wgpuTextureGetWidth (surface_texture.texture); Uint32 surface_height = wgpuTextureGetHeight(surface_texture.texture); if (surface_width != window_width || surface_height != window_height) { wgpuTextureViewRelease(surface_texture_view); wgpuTextureRelease(surface_texture.texture); ImGui::EndFrame(); surface_configuration.width = window_width; surface_configuration.height = window_height; wgpuSurfaceConfigure(surface, &surface_configuration); continue; } if (framebuffer_width != surface_width || framebuffer_height != surface_height) { if (framebuffer) wgpuTextureRelease(framebuffer); WGPUTextureDescriptor descriptor = { .label = { .data = "framebuffer", .length = WGPU_STRLEN }, .usage = WGPUTextureUsage_RenderAttachment, .dimension = WGPUTextureDimension_2D, .size = { .width = surface_width, .height = surface_height, .depthOrArrayLayers = 1 }, .format = surface_configuration.format, .mipLevelCount = 1, .sampleCount = 4, .viewFormatCount = 0, .viewFormats = NULL, }; framebuffer = wgpuDeviceCreateTexture(device, &descriptor); framebuffer_width = surface_width; framebuffer_height = surface_height; } WGPUTextureView framebuffer_view = wgpuTextureCreateView(framebuffer, NULL); WGPUCommandEncoderDescriptor command_encoder_descriptor = {}; WGPUCommandEncoder command_encoder = wgpuDeviceCreateCommandEncoder(device, &command_encoder_descriptor); { ZoneScopedN("Update"); { ZoneScopedN("player_instance_buffer"); player_instance.pos.x = player.pos_x; player_instance.pos.y = player.pos_y; update_buffer(player_instance_buffer, 0, sizeof(player_instance), &player_instance); } { ZoneScopedN("per_frame"); float aspect_ratio = ((float) window_width / (float) window_height); view_matrix = view (V3_((float)player.pos_x, (float)player.pos_y, 0), radians(camera_tilt), camera_distance); inverse_view_matrix = inverse_view(V3_((float)player.pos_x, (float)player.pos_y, 0), radians(camera_tilt), camera_distance); projection_matrix = projection (radians(camera_fovy_degrees), aspect_ratio, NEAR_PLANE); inverse_projection_matrix = inverse_projection(radians(camera_fovy_degrees), aspect_ratio, NEAR_PLANE); M4x4 view_projection_matrix = projection_matrix * view_matrix; update_buffer(view_projection_matrix_buffer, 0, sizeof(view_projection_matrix), &view_projection_matrix); V2 floor_intersection = get_floor_intersection_of_mouse(mouse_pos); Sint32x2 tile_pos = grid_tile_pos_from_floor_intersection(floor_intersection); per_frame.map_width = current_map.width; per_frame.grid_width = selected_tile != -1 ? current_map.width : current_map.width + 1; per_frame.grid_offset = selected_tile != -1 ? V2_(-0.5f, -0.5f) : V2_(-1.0f, -1.0f); per_frame.mouse = tile_pos; if (dragging_tile_change && selected_tile != -1) { Sint32x2 grid_tile_pos = grid_tile_pos_from_floor_intersection(drag_start_pos); per_frame.drag_start = grid_tile_pos; } else { per_frame.drag_start = tile_pos; } update_buffer(per_frame_buffer, 0, sizeof(per_frame), &per_frame); } { ZoneScopedN("tint color"); Sint64 tint_times_ns[MAX_TINT_TIMES]; for (int i = 0; i < num_used_tint_times; i++) tint_times_ns[i] = (time_tints_times[i][0] * 60 * 60 + time_tints_times[i][1] * 60 + time_tints_times[i][2]) * SDL_NS_PER_SECOND; tint_times_ns[num_used_tint_times] = (24 * 60 * 60 + 60 * 60 + 60) * SDL_NS_PER_SECOND + tint_times_ns[0]; Sint64 calendar_time_ns = (calendar_time.hour * 60 * 60 + calendar_time.minute * 60 + calendar_time.second) * SDL_NS_PER_SECOND + (Sint64)calendar_time.nanosecond; int last_time_index = num_used_tint_times - 1; for (int i = 0; i < num_used_tint_times; i++) { if (calendar_time_ns > tint_times_ns[i]) last_time_index = i; } if (calendar_time_ns <= tint_times_ns[0]) calendar_time_ns += (24 * 60 * 60 + 60 * 60 + 60) * SDL_NS_PER_SECOND; Sint64 v = calendar_time_ns - tint_times_ns[last_time_index]; Sint64 time_between = tint_times_ns[last_time_index + 1] - tint_times_ns[last_time_index]; double t = v / (double)time_between; V3 tint_color = lerp(time_tints[last_time_index], time_tints[(last_time_index + 1) % num_used_tint_times], t); update_buffer(tint_color_buffer, 0, sizeof(tint_color), &tint_color); } } WGPURenderPassColorAttachment render_pass_color_attachment = { .view = framebuffer_view, .depthSlice = 0, .resolveTarget = surface_texture_view, .loadOp = WGPULoadOp_Clear, .storeOp = WGPUStoreOp_Discard, .clearValue = { .r = 0.01f, .g = 0.01f, .b = 0.01f, .a = 1.0f }, }; WGPURenderPassDescriptor render_pass_descriptor = { .label = { .data = "main_render_pass", .length = WGPU_STRLEN }, .colorAttachmentCount = 1, .colorAttachments = &render_pass_color_attachment, .depthStencilAttachment = NULL, .occlusionQuerySet = NULL, .timestampWrites = NULL, }; WGPURenderPassEncoder render_pass_encoder = wgpuCommandEncoderBeginRenderPass(command_encoder, &render_pass_descriptor); wgpuRenderPassEncoderSetBindGroup(render_pass_encoder, 0, per_frame_bind_group, 0, NULL); { // Draw Map ZoneScopedN("Draw Map"); wgpuRenderPassEncoderSetPipeline(render_pass_encoder, world_render_pipeline); wgpuRenderPassEncoderSetIndexBuffer(render_pass_encoder, index_buffer, WGPUIndexFormat_Uint16, 0, WGPU_WHOLE_SIZE); wgpuRenderPassEncoderSetVertexBuffer(render_pass_encoder, 0, vertex_buffer, 0, WGPU_WHOLE_SIZE); wgpuRenderPassEncoderSetVertexBuffer(render_pass_encoder, 1, current_map.gpu_buffer, 0, WGPU_WHOLE_SIZE); wgpuRenderPassEncoderSetBindGroup(render_pass_encoder, 1, world_bind_group, 0, NULL); wgpuRenderPassEncoderDrawIndexed(render_pass_encoder, 6, current_map.height * current_map.width, 0, 0, 0); } { // Draw Player ZoneScopedN("Draw Player"); wgpuRenderPassEncoderSetPipeline(render_pass_encoder, basic_render_pipeline); wgpuRenderPassEncoderSetIndexBuffer(render_pass_encoder, index_buffer, WGPUIndexFormat_Uint16, 0, WGPU_WHOLE_SIZE); wgpuRenderPassEncoderSetVertexBuffer(render_pass_encoder, 0, vertex_buffer, 0, WGPU_WHOLE_SIZE); wgpuRenderPassEncoderSetVertexBuffer(render_pass_encoder, 1, player_instance_buffer, 0, WGPU_WHOLE_SIZE); wgpuRenderPassEncoderSetBindGroup(render_pass_encoder, 1, basic_bind_group, 0, NULL); wgpuRenderPassEncoderDrawIndexed(render_pass_encoder, 6, 1, 0, 0, 0); } if (show_tile_picker) { // Draw Grid ZoneScopedN("Draw Grid"); Uint32 num_grid_cells = current_map.height * current_map.width; if (selected_tile == -1) num_grid_cells = (current_map.height + 1) * (current_map.width + 1); wgpuRenderPassEncoderSetPipeline(render_pass_encoder, grid_render_pipeline); wgpuRenderPassEncoderSetIndexBuffer(render_pass_encoder, grid_index_buffer, WGPUIndexFormat_Uint16, 0, WGPU_WHOLE_SIZE); wgpuRenderPassEncoderSetVertexBuffer(render_pass_encoder, 0, grid_vertex_buffer, 0, WGPU_WHOLE_SIZE); wgpuRenderPassEncoderDrawIndexed(render_pass_encoder, SDL_arraysize(grid_indices), num_grid_cells, 0, 0, 0); } { ZoneScopedN("ImGui Render"); ImGui::Render(); ImDrawData *draw_data = ImGui::GetDrawData(); ImGui_ImplWGPU_RenderDrawData(draw_data, render_pass_encoder); } wgpuRenderPassEncoderEnd(render_pass_encoder); wgpuRenderPassEncoderRelease(render_pass_encoder); { ZoneScopedN("SubmitGPUCommandBuffer"); WGPUCommandBufferDescriptor command_buffer_descriptor = {}; WGPUCommandBuffer command_buffer = wgpuCommandEncoderFinish(command_encoder, &command_buffer_descriptor); wgpuCommandEncoderRelease(command_encoder); wgpuQueueSubmit(queue, 1, &command_buffer); wgpuCommandBufferRelease(command_buffer); } { ZoneScopedN("wgpuSurfacePresent"); wgpuSurfacePresent(surface); } wgpuTextureViewRelease(framebuffer_view); wgpuTextureViewRelease(surface_texture_view); wgpuTextureRelease(surface_texture.texture); FrameMark; } ImGui_ImplWGPU_Shutdown(); ImGui_ImplSDL3_Shutdown(); ImGui::DestroyContext(); return 0; }