2597 lines
101 KiB
C++
2597 lines
101 KiB
C++
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <bit>
|
|
#include <SDL3/SDL.h>
|
|
#include <SDL3_mixer/SDL_mixer.h>
|
|
#include <imgui.h>
|
|
#include <imgui_internal.h>
|
|
#include <imgui_impl_sdl3.h>
|
|
#include <imgui_impl_wgpu.h>
|
|
#include <tracy/Tracy.hpp>
|
|
#include <tracy/TracyC.h>
|
|
#include <webgpu/webgpu.h>
|
|
|
|
#include <glm/glm.hpp>
|
|
#include <glm/gtc/type_ptr.hpp>
|
|
|
|
#include "defer.h"
|
|
#include "stb_image.h"
|
|
#include "change_directory.h"
|
|
|
|
#include "shaders/shaders.h"
|
|
|
|
using namespace glm;
|
|
|
|
#define ASSETS_PATH "./assets/"
|
|
|
|
#define NEAR_PLANE (0.01f)
|
|
|
|
#define TILE_SIZE (32)
|
|
#define TILE_ATLAS_SIZE (256)
|
|
|
|
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 player_texture;
|
|
static WGPUTextureView player_texture_view;
|
|
|
|
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;
|
|
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_uvs_buffer;
|
|
|
|
static WGPUSurfaceConfiguration surface_configuration;
|
|
|
|
static i32vec2 window_size = { 1280, 720 };
|
|
static i32vec2 framebuffer_size;
|
|
|
|
static MIX_Mixer *mixer;
|
|
static MIX_Track *music_track;
|
|
static MIX_Track *sfx_track;
|
|
static MIX_Audio *music_setting_off_piano;
|
|
|
|
static float volume_master = 50.0f;
|
|
static float volume_music = 50.0f;
|
|
static float volume_sfx = 50.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 mat4x4 view_matrix;
|
|
static mat4x4 inverse_view_matrix;
|
|
|
|
static mat4x4 projection_matrix;
|
|
static mat4x4 inverse_projection_matrix;
|
|
|
|
static SDL_Time time;
|
|
|
|
static bool enable_time_tints = true;
|
|
static bool use_actual_time = true;
|
|
static SDL_DateTime calendar_time;
|
|
|
|
static vec2 mouse_pos;
|
|
|
|
static bool in_editor;
|
|
|
|
static vec2 editor_camera_position;
|
|
static float editor_camera_distance = 30.0f;
|
|
|
|
static bool show_demo_window = false;
|
|
static bool show_tile_picker = false;
|
|
static bool show_settings = false;
|
|
|
|
#define log_error(...) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
|
|
|
float remap(float in_a, float in_b, float out_a, float out_b, float v) {
|
|
return mix(out_a, out_b, (v - in_a) / (in_b - in_a));
|
|
}
|
|
|
|
vec2 remap(vec2 in_a, vec2 in_b, vec2 out_a, vec2 out_b, vec2 v) {
|
|
return mix(out_a, out_b, (v - in_a) / (in_b - in_a));
|
|
}
|
|
|
|
mat4x4 view(vec3 player_pos, float tilt, float camera_distance) {
|
|
float s = sinf(tilt);
|
|
float c = cosf(tilt);
|
|
|
|
return {
|
|
1, 0, 0, -player_pos.x,
|
|
0, c, s, c * -player_pos.y,
|
|
0, -s, c, s * player_pos.y - camera_distance,
|
|
0, 0, 0, 1,
|
|
};
|
|
}
|
|
|
|
mat4x4 inverse_view(vec3 player_pos, float tilt, float camera_distance) {
|
|
float s = sinf(tilt);
|
|
float c = cosf(tilt);
|
|
|
|
return {
|
|
1, 0, 0, player_pos.x,
|
|
0, c, -s, -s * camera_distance + player_pos.y,
|
|
0, s, c, c * camera_distance,
|
|
0, 0, 0, 1,
|
|
};
|
|
}
|
|
|
|
mat4x4 projection(float fovy, float aspect, float near) {
|
|
float g = 1.0 / tanf(fovy * 0.5);
|
|
|
|
return {
|
|
g / aspect, 0, 0, 0,
|
|
0, g, 0, 0,
|
|
0, 0, 0, near,
|
|
0, 0, -1, 0,
|
|
};
|
|
}
|
|
|
|
mat4x4 inverse_projection(float fovy, float aspect, float near) {
|
|
float g = 1.0 / tanf(fovy * 0.5);
|
|
|
|
return {
|
|
aspect / g, 0, 0, 0,
|
|
0, 1 / g, 0, 0,
|
|
0, 0, 0, -1,
|
|
0, 0, 1 / near, 0,
|
|
};
|
|
}
|
|
|
|
#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 vec3 time_tints[MAX_TINT_TIMES] = {
|
|
{ 0.314f, 0.369f, 0.455f },
|
|
{ 1.0f, 0.891f, 0.868f },
|
|
{ 1.0f, 0.465f, 0.373f },
|
|
{ 0.314f, 0.369f, 0.455f },
|
|
};
|
|
|
|
enum Settings_Category {
|
|
SETTINGS_UNKNOWN,
|
|
SETTINGS_AUDIO,
|
|
};
|
|
|
|
struct Vertex {
|
|
vec3 pos;
|
|
vec2 uv;
|
|
};
|
|
|
|
static Vertex vertices[] = {
|
|
{{ -0.5f, 0.5f, 0.0f }, { 0.0f, 0.0f }},
|
|
{{ -0.5f, -0.5f, 0.0f }, { 0.0f, 1.0f }},
|
|
{{ 0.5f, -0.5f, 0.0f }, { 1.0f, 1.0f }},
|
|
{{ 0.5f, 0.5f, 0.0f }, { 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.0f }},
|
|
{{ -0.5f, -0.5f + grid_line_size, 0.0f }},
|
|
{{ -0.5f + grid_line_size, -0.5f + grid_line_size, 0.0f }},
|
|
{{ -0.5f + grid_line_size, 0.5f, 0.0f }},
|
|
|
|
// TOP
|
|
{{ -0.5f + grid_line_size, 0.5f, 0.0f }},
|
|
{{ -0.5f + grid_line_size, 0.5f - grid_line_size, 0.0f }},
|
|
{{ 0.5f, 0.5f - grid_line_size, 0.0f }},
|
|
{{ 0.5f, 0.5f, 0.0f }},
|
|
|
|
// RIGHT
|
|
{{ 0.5f - grid_line_size, 0.5f - grid_line_size, 0.0f }},
|
|
{{ 0.5f - grid_line_size, -0.5f, 0.0f }},
|
|
{{ 0.5f, -0.5f, 0.0f }},
|
|
{{ 0.5f, 0.5f - grid_line_size, 0.0f }},
|
|
|
|
// BOTTOM
|
|
{{ -0.5f, -0.5f + grid_line_size, 0.0f }},
|
|
{{ -0.5f, -0.5f, 0.0f }},
|
|
{{ 0.5f - grid_line_size, -0.5f, 0.0f }},
|
|
{{ 0.5f - grid_line_size, -0.5f + grid_line_size, 0.0f }},
|
|
};
|
|
|
|
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 {
|
|
vec2 pos;
|
|
};
|
|
|
|
static Instance player_instance = {{ 0.0f, 0.0f }};
|
|
|
|
struct Map {
|
|
Uint32 version;
|
|
|
|
i32vec2 size;
|
|
Uint32 *tiles;
|
|
|
|
char name[64];
|
|
WGPUBuffer gpu_buffer;
|
|
};
|
|
|
|
static Map current_map;
|
|
|
|
struct Player {
|
|
i32vec2 position;
|
|
};
|
|
|
|
static Player player;
|
|
|
|
struct PerFrame {
|
|
i32vec2 drag_start;
|
|
i32vec2 mouse;
|
|
vec2 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 vec4 tile_uvs[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;
|
|
static bool dragging_camera_change;
|
|
static vec2 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;
|
|
}
|
|
|
|
#define MAP_FILE_VERSION (1u)
|
|
|
|
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_WriteU32LE(file, MAP_FILE_VERSION)) {
|
|
log_error("Failed to write version to map file.");
|
|
return false;
|
|
}
|
|
|
|
if (!SDL_WriteS32LE(file, map.size.x)) {
|
|
log_error("Failed to write width to map file.");
|
|
return false;
|
|
}
|
|
|
|
if (!SDL_WriteS32LE(file, map.size.y)) {
|
|
log_error("Failed to write height to map file.");
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < map.size.x * map.size.y; 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;
|
|
};
|
|
|
|
SDL_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));
|
|
SDL_memcpy(result->name, name, SDL_min(strlen(name), SDL_arraysize(result->name) - 1));
|
|
|
|
if (!SDL_ReadU32LE(file, &result->version)) {
|
|
log_error("Failed read version from map file.");
|
|
return false;
|
|
}
|
|
|
|
if (result->version > MAP_FILE_VERSION) {
|
|
log_error("Map file version (%u) is higher than the highest supported.", result->version);
|
|
return false;
|
|
}
|
|
|
|
if (!SDL_ReadS32LE(file, &result->size.x)) {
|
|
log_error("Failed read width from map file.");
|
|
return false;
|
|
}
|
|
|
|
if (!SDL_ReadS32LE(file, &result->size.y)) {
|
|
log_error("Failed read height from map file.");
|
|
return false;
|
|
}
|
|
|
|
result->tiles = (Uint32*)malloc(result->size.x * result->size.y * sizeof(Uint32));
|
|
|
|
for (int i = 0; i < result->size.x * result->size.y; 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->size.x * result->size.y * 4, result->tiles, buffer_name);
|
|
if (!result->gpu_buffer) {
|
|
log_error("Failed to create buffer. Exiting.");
|
|
return 1;
|
|
}
|
|
|
|
SDL_Log("Loaded map file.");
|
|
return true;
|
|
}
|
|
|
|
static void unload_map(Map *map) {
|
|
map->size = i32vec2(0, 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->size.x;
|
|
Sint32 old_map_height = map->size.y;
|
|
|
|
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->size.x;
|
|
Sint32 to_fill_height = map->size.y;
|
|
Sint32 to_fill_x_offset = 0;
|
|
Sint32 to_fill_y_offset = 0;
|
|
|
|
if (direction == 'W') {
|
|
player.position.x = player.position.x + amount;
|
|
map->size.x += amount;
|
|
to_fill_width = amount;
|
|
|
|
if (amount < 0)
|
|
old_x_offset = -amount;
|
|
else
|
|
new_x_offset = amount;
|
|
}
|
|
|
|
if (direction == 'N') {
|
|
player.position.y = player.position.y + amount;
|
|
map->size.y += amount;
|
|
to_fill_height = amount;
|
|
|
|
if (amount < 0)
|
|
old_y_offset = -amount;
|
|
else
|
|
new_y_offset = amount;
|
|
}
|
|
|
|
if (direction == 'E') {
|
|
map->size.x += amount;
|
|
to_fill_width = amount;
|
|
to_fill_x_offset = old_map_width;
|
|
}
|
|
|
|
if (direction == 'S') {
|
|
map->size.y += amount;
|
|
to_fill_height = amount;
|
|
to_fill_y_offset = old_map_height;
|
|
}
|
|
|
|
map->tiles = (Uint32*)malloc(map->size.x * map->size.y * sizeof(Uint32));
|
|
|
|
for (int y = 0; y < min(old_map_height, map->size.y); y++) {
|
|
for (int x = 0; x < min(old_map_width, map->size.x); x++) {
|
|
map->tiles[(y + new_y_offset) * map->size.x + (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->size.x + (x + to_fill_x_offset)] = 1;
|
|
}
|
|
}
|
|
|
|
player.position = clamp(player.position, i32vec2(0, 0), map->size - 2);
|
|
|
|
map->gpu_buffer = create_buffer(WGPUBufferUsage_Vertex, map->size.x * map->size.y * 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, 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();
|
|
|
|
vec4 uv = tile_uvs[tile_index] / (float)TILE_ATLAS_SIZE;
|
|
|
|
switch (orientation) {
|
|
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;
|
|
}
|
|
|
|
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 vec3 Unproject(vec3 screen_pos) {
|
|
vec4 result = vec4(screen_pos, 1.0f) * inverse_projection_matrix * inverse_view_matrix;
|
|
result.x /= result.w;
|
|
result.y /= result.w;
|
|
result.z /= result.w;
|
|
|
|
return result.xyz();
|
|
}
|
|
|
|
static vec2 get_floor_intersection_of_mouse(vec2 mouse_pos) {
|
|
vec2 mouse = remap(vec2(0, 0), window_size, vec2(-1, 1), vec2(1, -1), mouse_pos);
|
|
vec3 camera_position = (vec4(0, 0, 0, 1) * inverse_view_matrix).xyz();
|
|
|
|
vec3 probe = Unproject(vec3(mouse, .5));
|
|
vec3 ray_dir = normalize(probe - camera_position);
|
|
|
|
float t = -camera_position.z / ray_dir.z;
|
|
vec3 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_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,
|
|
},
|
|
},
|
|
{
|
|
.binding = 3,
|
|
.visibility = WGPUShaderStage_Fragment,
|
|
|
|
.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,
|
|
},
|
|
{
|
|
.format = WGPUVertexFormat_Float32x2,
|
|
.offset = offsetof(Vertex, uv),
|
|
.shaderLocation = 1,
|
|
},
|
|
};
|
|
|
|
WGPUVertexAttribute instance_buffer_attributes[] = {
|
|
{
|
|
.format = WGPUVertexFormat_Uint32,
|
|
.offset = 0,
|
|
.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(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_atlas", .length = WGPU_STRLEN },
|
|
.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst,
|
|
.dimension = WGPUTextureDimension_2D,
|
|
.size = { .width = TILE_ATLAS_SIZE, .height = TILE_ATLAS_SIZE, .depthOrArrayLayers = 1 },
|
|
.format = WGPUTextureFormat_RGBA8UnormSrgb,
|
|
.mipLevelCount = 1,
|
|
.sampleCount = 1,
|
|
.viewFormatCount = SDL_arraysize(view_formats),
|
|
.viewFormats = view_formats,
|
|
};
|
|
|
|
tile_textures_atlas = wgpuDeviceCreateTexture(device, &descriptor);
|
|
if (!tile_textures_atlas) {
|
|
log_error("Failed to create texture.");
|
|
return false;
|
|
}
|
|
tile_textures_atlas_view = wgpuTextureCreateView(tile_textures_atlas, NULL);
|
|
|
|
WGPUTextureViewDescriptor unorm_view_descriptor = {
|
|
.format = WGPUTextureFormat_RGBA8Unorm,
|
|
.dimension = WGPUTextureViewDimension_2D,
|
|
.mipLevelCount = 1,
|
|
.baseArrayLayer = 0,
|
|
.arrayLayerCount = 1,
|
|
};
|
|
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;
|
|
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_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_atlas,
|
|
.mipLevel = 0,
|
|
.origin = { .x = x, .y = y, .z = 0 },
|
|
.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);
|
|
}
|
|
|
|
tile_uvs_buffer = create_buffer(WGPUBufferUsage_Storage | WGPUBufferUsage_CopyDst, sizeof(tile_uvs), tile_uvs, "tile_uvs_buffer");
|
|
|
|
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.size.x || tile_pos_y < 0 || tile_pos_y >= current_map.size.y)
|
|
return 0;
|
|
|
|
Uint32 tile = current_map.tiles[tile_pos_y * current_map.size.x + 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) {
|
|
const Uint32 INFO_NONE = ((TILEKIND_NONE << 24) | (TILEKIND_NONE << 16) | (TILEKIND_NONE << 8) | TILEKIND_NONE);
|
|
const Uint32 INFO_ERROR = ((TILEKIND_ERROR << 24) | (TILEKIND_ERROR << 16) | (TILEKIND_ERROR << 8) | TILEKIND_ERROR);
|
|
const Uint32 INFO_MASKS[4] = { 0x0000ff00, 0x000000ff, 0xff000000, 0x00ff0000 };
|
|
|
|
Uint32 corner_infos[4] = { get_corner_info(pos_x + 0, pos_y + 1), get_corner_info(pos_x + 1, pos_y + 1), get_corner_info(pos_x + 1, pos_y + 0), get_corner_info(pos_x + 0, pos_y + 0) };
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
Uint32 replace_mask = INFO_MASKS[i];
|
|
if ((corner_infos[i] == INFO_NONE) | (corner_infos[i] == INFO_ERROR))
|
|
replace_mask = 0xffffffff;
|
|
|
|
corner_infos[i] = corner_infos[i] ^ ((corner_infos[i] ^ ((kind << 24) | (kind << 16) | (kind << 8) | kind)) & replace_mask);
|
|
}
|
|
|
|
if (0 <= pos_x + 0 && pos_x + 0 < current_map.size.x && 0 <= pos_y + 1 && pos_y + 1 < current_map.size.y)
|
|
current_map.tiles[(pos_y + 1) * current_map.size.x + pos_x + 0] = find_matching_tile(corner_infos[0]);
|
|
|
|
if (0 <= pos_x + 1 && pos_x + 1 < current_map.size.x && 0 <= pos_y + 1 && pos_y + 1 < current_map.size.y)
|
|
current_map.tiles[(pos_y + 1) * current_map.size.x + pos_x + 1] = find_matching_tile(corner_infos[1]);
|
|
|
|
if (0 <= pos_x + 1 && pos_x + 1 < current_map.size.x && 0 <= pos_y + 0 && pos_y + 0 < current_map.size.y)
|
|
current_map.tiles[(pos_y + 0) * current_map.size.x + pos_x + 1] = find_matching_tile(corner_infos[2]);
|
|
|
|
if (0 <= pos_x + 0 && pos_x + 0 < current_map.size.x && 0 <= pos_y + 0 && pos_y + 0 < current_map.size.y)
|
|
current_map.tiles[(pos_y + 0) * current_map.size.x + pos_x + 0] = find_matching_tile(corner_infos[3]);
|
|
|
|
update_buffer(current_map.gpu_buffer, 0, current_map.size.x * current_map.size.y * 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 i32vec2 grid_tile_pos_from_floor_intersection(vec2 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)),
|
|
};
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
static 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);
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
static bool init_webgpu() {
|
|
instance = wgpuCreateInstance(NULL);
|
|
if (!instance) {
|
|
log_error("Failed to create webgpu instance.");
|
|
return false;
|
|
}
|
|
|
|
surface = create_wgpu_surface_for_SDL_window(window);
|
|
if (!surface) {
|
|
log_error("Failed to create webgpu surface for SDL window.");
|
|
return false;
|
|
}
|
|
|
|
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 get webgpu device.");
|
|
return false;
|
|
}
|
|
|
|
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.");
|
|
return false;
|
|
}
|
|
|
|
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 = 1,
|
|
};
|
|
|
|
pixel_sampler = wgpuDeviceCreateSampler(device, &pixel_sampler_descriptor);
|
|
|
|
view_projection_matrix_buffer = create_buffer(WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst, sizeof(mat4x4), NULL, "view_projection_matrix_buffer");
|
|
if (!view_projection_matrix_buffer) {
|
|
log_error("Failed to create buffer.");
|
|
return false;
|
|
}
|
|
|
|
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.");
|
|
return false;
|
|
}
|
|
|
|
tint_color_buffer = create_buffer(WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst, sizeof(vec3), NULL, "tint_color_buffer");
|
|
if (!tint_color_buffer) {
|
|
log_error("Failed to create buffer.");
|
|
return false;
|
|
}
|
|
|
|
vertex_buffer = create_buffer(WGPUBufferUsage_Vertex, sizeof(vertices), vertices, "vertex_buffer");
|
|
if (!vertex_buffer) {
|
|
log_error("Failed to create buffer.");
|
|
return false;
|
|
}
|
|
|
|
index_buffer = create_buffer(WGPUBufferUsage_Index, sizeof(indices), indices, "index_buffer");
|
|
if (!index_buffer) {
|
|
log_error("Failed to create buffer.");
|
|
return false;
|
|
}
|
|
|
|
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.");
|
|
return false;
|
|
}
|
|
|
|
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.");
|
|
return false;
|
|
}
|
|
|
|
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.");
|
|
return false;
|
|
}
|
|
|
|
player_texture = create_shader_texture("decorations/strawberry.png");
|
|
if (!player_texture) {
|
|
log_error("Failed to create shader texture.");
|
|
return false;
|
|
}
|
|
|
|
player_texture_view = wgpuTextureCreateView(player_texture, NULL);
|
|
|
|
if (!recreate_tile_textures()) {
|
|
log_error("Failed to create tile textures.");
|
|
return false;
|
|
}
|
|
|
|
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_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 = {
|
|
.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);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void setup_working_directory() {
|
|
if (SDL_GetPathInfo(ASSETS_PATH, NULL)) return;
|
|
|
|
const char *current_directory = SDL_GetCurrentDirectory();
|
|
|
|
change_directory(SDL_GetBasePath());
|
|
if (SDL_GetPathInfo(ASSETS_PATH, NULL)) return;
|
|
|
|
change_directory("..");
|
|
if (SDL_GetPathInfo(ASSETS_PATH, NULL)) return;
|
|
|
|
change_directory(current_directory);
|
|
}
|
|
|
|
static void process_event_editor(SDL_Event event) {
|
|
ZoneScopedN("process_event_editor");
|
|
|
|
ImGuiIO &io = ImGui::GetIO();
|
|
|
|
ImGui_ImplSDL3_ProcessEvent(&event);
|
|
|
|
switch (event.type) {
|
|
case SDL_EVENT_KEY_DOWN: {
|
|
if (event.key.key == SDLK_F9) {
|
|
in_editor = !in_editor;
|
|
}
|
|
|
|
if (io.WantCaptureKeyboard)
|
|
return;
|
|
|
|
SDL_Keymod modifiers = SDL_GetModState();
|
|
|
|
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_WHEEL: {
|
|
if (io.WantCaptureMouse)
|
|
return;
|
|
|
|
vec2 floor_pos_before = get_floor_intersection_of_mouse(vec2(event.wheel.mouse_x, event.wheel.mouse_y));
|
|
editor_camera_distance = SDL_max(0.2, editor_camera_distance - event.wheel.y * SDL_sqrt(editor_camera_distance));
|
|
|
|
view_matrix = view (vec3(editor_camera_position, 0), 0, editor_camera_distance);
|
|
inverse_view_matrix = inverse_view(vec3(editor_camera_position, 0), 0, editor_camera_distance);
|
|
|
|
vec2 floor_pos_after = get_floor_intersection_of_mouse(vec2(event.wheel.mouse_x, event.wheel.mouse_y));
|
|
|
|
editor_camera_position += floor_pos_before - floor_pos_after;
|
|
|
|
view_matrix = view (vec3(editor_camera_position, 0), 0, editor_camera_distance);
|
|
inverse_view_matrix = inverse_view(vec3(editor_camera_position, 0), 0, editor_camera_distance);
|
|
} break;
|
|
|
|
case SDL_EVENT_MOUSE_BUTTON_DOWN: {
|
|
if (io.WantCaptureMouse)
|
|
return;
|
|
|
|
vec2 floor_intersection = get_floor_intersection_of_mouse(vec2(event.button.x, event.button.y));
|
|
i32vec2 tile_pos = grid_tile_pos_from_floor_intersection(floor_intersection);
|
|
|
|
drag_start_pos = floor_intersection;
|
|
|
|
if (event.button.button == SDL_BUTTON_RIGHT) {
|
|
dragging_camera_change = true;
|
|
}
|
|
|
|
if (event.button.button == SDL_BUTTON_LEFT) {
|
|
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.size.x && -1 <= tile_pos.y && tile_pos.y < current_map.size.y) {
|
|
dragging_tile_change = true;
|
|
}
|
|
}
|
|
|
|
if (0 <= tile_pos.x && tile_pos.x < current_map.size.x && 0 <= tile_pos.y && tile_pos.y < current_map.size.y) {
|
|
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.size.x) {
|
|
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.size.y) {
|
|
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)
|
|
return;
|
|
|
|
if (event.button.button == SDL_BUTTON_RIGHT) {
|
|
dragging_camera_change = false;
|
|
}
|
|
|
|
if (event.button.button == SDL_BUTTON_LEFT) {
|
|
if (selected_tile != -1 && dragging_tile_change) {
|
|
vec2 floor_intersection = get_floor_intersection_of_mouse(vec2(event.button.x, event.button.y));
|
|
i32vec2 tile_pos = grid_tile_pos_from_floor_intersection(floor_intersection);
|
|
|
|
Sint32 tile_x = clamp(0, tile_pos.x, current_map.size.x - 1);
|
|
Sint32 tile_y = clamp(0, tile_pos.y, current_map.size.y - 1);
|
|
|
|
i32vec2 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.size.x * y] = ((rotation & 3) << 16) | selected_tile;
|
|
} else {
|
|
current_map.tiles[x + current_map.size.x * y] = ((selected_rotation & 3) << 16) | selected_tile;
|
|
}
|
|
}
|
|
}
|
|
|
|
update_buffer(current_map.gpu_buffer, 0, current_map.size.x * current_map.size.y * sizeof(Uint32), current_map.tiles);
|
|
}
|
|
|
|
dragging_tile_change = false;
|
|
}
|
|
} break;
|
|
|
|
case SDL_EVENT_MOUSE_MOTION: {
|
|
mouse_pos = vec2(event.motion.x, event.motion.y);
|
|
vec2 floor_intersection = get_floor_intersection_of_mouse(mouse_pos);
|
|
|
|
if (dragging_camera_change) {
|
|
editor_camera_position -= (floor_intersection - drag_start_pos);
|
|
|
|
view_matrix = view (vec3(editor_camera_position, 0), 0, editor_camera_distance);
|
|
inverse_view_matrix = inverse_view(vec3(editor_camera_position, 0), 0, editor_camera_distance);
|
|
}
|
|
|
|
if (selected_tile_kind != -1 && dragging_tile_change) {
|
|
vec2 floor_intersection = get_floor_intersection_of_mouse(mouse_pos);
|
|
i32vec2 tile_pos = grid_tile_pos_from_floor_intersection(floor_intersection);
|
|
change_map_tile(tile_pos.x, tile_pos.y, (TileKind)selected_tile_kind);
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
static void update_state_editor() {
|
|
ZoneScopedN("update_state_editor");
|
|
|
|
ImGui_ImplWGPU_NewFrame();
|
|
ImGui_ImplSDL3_NewFrame();
|
|
ImGui::NewFrame();
|
|
|
|
if (ImGui::BeginMainMenuBar()) {
|
|
if (ImGui::BeginMenu("File")) {
|
|
if (ImGui::MenuItem("Save")) {
|
|
save_map(current_map);
|
|
}
|
|
|
|
if (ImGui::MenuItem("Reload")) {
|
|
recreate_tile_textures();
|
|
WGPUBindGroupEntry world_bind_group_entries[] = {
|
|
{ .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 = {
|
|
.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);
|
|
}
|
|
|
|
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_SetMixerGain(mixer, volume_master / 100.0f);
|
|
};
|
|
if (ImGui::DragFloat("Music", &volume_music, 1.0f, 0.0f, 100.0f, "%.0f", ImGuiSliderFlags_AlwaysClamp)) {
|
|
MIX_SetTrackGain(music_track, volume_music / 100.0f);
|
|
}
|
|
if (ImGui::DragFloat("SFX", &volume_sfx, 1.0f, 0.0f, 100.0f, "%.0f", ImGuiSliderFlags_AlwaysClamp)) {
|
|
MIX_SetTrackGain(sfx_track, 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::Checkbox("enable time based tinting", &enable_time_tints);
|
|
|
|
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", glm::value_ptr(time_tints[i]));
|
|
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] };
|
|
vec3 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);
|
|
|
|
if (i != 0)
|
|
SameLineOrWrap(ImVec2(32, 32));
|
|
|
|
if (SelectableTile("##tile", selected_tile == i, i, ImVec2(32, 32), SDL_max(selected_rotation, 0))) {
|
|
selected_tile_kind = -1;
|
|
selected_tile = i;
|
|
}
|
|
|
|
ImGui::PopID();
|
|
}
|
|
|
|
if (selected_tile != -1) {
|
|
ImGui::Text("Rotation:");
|
|
|
|
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), 1))
|
|
selected_rotation = 1;
|
|
|
|
SameLineOrWrap(ImVec2(32, 32));
|
|
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), 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);
|
|
}
|
|
|
|
static void render_editor(WGPURenderPassColorAttachment framebuffer) {
|
|
ZoneScopedN("render_editor");
|
|
|
|
{
|
|
ZoneScopedN("update buffers");
|
|
{
|
|
ZoneScopedN("player_instance_buffer");
|
|
player_instance.pos = player.position;
|
|
|
|
update_buffer(player_instance_buffer, 0, sizeof(player_instance), &player_instance);
|
|
}
|
|
|
|
{
|
|
ZoneScopedN("per_frame");
|
|
|
|
float aspect_ratio = ((float)window_size.x / (float)window_size.y);
|
|
|
|
view_matrix = view (vec3(editor_camera_position, 0), 0, editor_camera_distance);
|
|
inverse_view_matrix = inverse_view(vec3(editor_camera_position, 0), 0, editor_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);
|
|
|
|
mat4x4 view_projection_matrix = view_matrix * projection_matrix;
|
|
update_buffer(view_projection_matrix_buffer, 0, sizeof(view_projection_matrix), &view_projection_matrix);
|
|
|
|
vec2 floor_intersection = get_floor_intersection_of_mouse(mouse_pos);
|
|
i32vec2 tile_pos = grid_tile_pos_from_floor_intersection(floor_intersection);
|
|
|
|
per_frame.map_width = current_map.size.x;
|
|
per_frame.grid_width = selected_tile != -1 ? current_map.size.x : current_map.size.x + 1;
|
|
per_frame.grid_offset = selected_tile != -1 ? vec2(-0.5f, -0.5f) : vec2(-1.0f, -1.0f);
|
|
per_frame.mouse = tile_pos;
|
|
|
|
if (dragging_tile_change && selected_tile != -1) {
|
|
i32vec2 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;
|
|
|
|
vec3 tint_color = mix(time_tints[last_time_index], time_tints[(last_time_index + 1) % num_used_tint_times], t);
|
|
if (!enable_time_tints) tint_color = vec3(1, 1, 1);
|
|
update_buffer(tint_color_buffer, 0, sizeof(tint_color), &tint_color);
|
|
}
|
|
}
|
|
|
|
WGPUCommandEncoder command_encoder = wgpuDeviceCreateCommandEncoder(device, NULL);
|
|
|
|
WGPURenderPassDescriptor render_pass_descriptor = {
|
|
.label = { .data = "main_render_pass", .length = WGPU_STRLEN },
|
|
.colorAttachmentCount = 1,
|
|
.colorAttachments = &framebuffer,
|
|
.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.size.y * current_map.size.x, 0, 0, 0);
|
|
}
|
|
|
|
if (show_tile_picker) { // Draw Grid
|
|
ZoneScopedN("Draw Grid");
|
|
|
|
Uint32 num_grid_cells = current_map.size.y * current_map.size.x;
|
|
if (selected_tile == -1)
|
|
num_grid_cells = (current_map.size.y + 1) * (current_map.size.x + 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);
|
|
}
|
|
}
|
|
|
|
static void process_event_game(SDL_Event event) {
|
|
ZoneScopedN("process_event");
|
|
|
|
switch (event.type) {
|
|
case SDL_EVENT_KEY_DOWN: {
|
|
if (event.key.key == SDLK_UP || event.key.key == SDLK_W) {
|
|
player.position.y = clamp(0, player.position.y + 1, current_map.size.y - 2);
|
|
}
|
|
|
|
if (event.key.key == SDLK_LEFT || event.key.key == SDLK_A) {
|
|
player.position.x = clamp(0, player.position.x - 1, current_map.size.x - 2);
|
|
}
|
|
|
|
if (event.key.key == SDLK_DOWN || event.key.key == SDLK_S) {
|
|
player.position.y = clamp(0, player.position.y - 1, current_map.size.y - 2);
|
|
}
|
|
|
|
if (event.key.key == SDLK_RIGHT || event.key.key == SDLK_D) {
|
|
player.position.x = clamp(0, player.position.x + 1, current_map.size.x - 2);
|
|
}
|
|
|
|
if (event.key.key == SDLK_F9) {
|
|
in_editor = !in_editor;
|
|
}
|
|
} break;
|
|
|
|
case SDL_EVENT_GAMEPAD_BUTTON_DOWN: {
|
|
if (event.gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_UP) {
|
|
player.position.y = clamp(0, player.position.y + 1, current_map.size.y - 2);
|
|
}
|
|
|
|
if (event.gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_LEFT) {
|
|
player.position.x = clamp(0, player.position.x - 1, current_map.size.x - 2);
|
|
}
|
|
|
|
if (event.gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_DOWN) {
|
|
player.position.y = clamp(0, player.position.y - 1, current_map.size.y - 2);
|
|
}
|
|
|
|
if (event.gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_RIGHT) {
|
|
player.position.x = clamp(0, player.position.x + 1, current_map.size.x - 2);
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
static void update_state_game() {
|
|
ZoneScopedN("update_state_game");
|
|
|
|
if (!MIX_TrackPlaying(music_track)) {
|
|
MIX_SetTrackAudio(music_track, music_setting_off_piano);
|
|
MIX_PlayTrack(music_track, 0);
|
|
}
|
|
}
|
|
|
|
static void render_game(WGPURenderPassColorAttachment framebuffer) {
|
|
ZoneScopedN("render_game");
|
|
|
|
{
|
|
ZoneScopedN("update buffers");
|
|
{
|
|
ZoneScopedN("player_instance_buffer");
|
|
player_instance.pos = player.position;
|
|
|
|
update_buffer(player_instance_buffer, 0, sizeof(player_instance), &player_instance);
|
|
}
|
|
|
|
{
|
|
ZoneScopedN("per_frame");
|
|
|
|
float aspect_ratio = ((float)window_size.x / (float)window_size.y);
|
|
|
|
view_matrix = view (vec3(player.position, 0), radians(camera_tilt), camera_distance);
|
|
inverse_view_matrix = inverse_view(vec3(player.position, 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);
|
|
|
|
mat4x4 view_projection_matrix = view_matrix * projection_matrix;
|
|
update_buffer(view_projection_matrix_buffer, 0, sizeof(view_projection_matrix), &view_projection_matrix);
|
|
|
|
per_frame.map_width = current_map.size.x;
|
|
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;
|
|
|
|
vec3 tint_color = mix(time_tints[last_time_index], time_tints[(last_time_index + 1) % num_used_tint_times], t);
|
|
if (!enable_time_tints) tint_color = vec3(1, 1, 1);
|
|
update_buffer(tint_color_buffer, 0, sizeof(tint_color), &tint_color);
|
|
}
|
|
}
|
|
|
|
WGPUCommandEncoder command_encoder = wgpuDeviceCreateCommandEncoder(device, NULL);
|
|
|
|
WGPURenderPassDescriptor render_pass_descriptor = {
|
|
.label = { .data = "main_render_pass", .length = WGPU_STRLEN },
|
|
.colorAttachmentCount = 1,
|
|
.colorAttachments = &framebuffer,
|
|
.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.size.y * current_map.size.x, 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);
|
|
}
|
|
|
|
wgpuRenderPassEncoderEnd(render_pass_encoder);
|
|
wgpuRenderPassEncoderRelease(render_pass_encoder);
|
|
|
|
{
|
|
ZoneScopedN("SubmitGPUCommandBuffer");
|
|
WGPUCommandBuffer command_buffer = wgpuCommandEncoderFinish(command_encoder, NULL);
|
|
wgpuCommandEncoderRelease(command_encoder);
|
|
wgpuQueueSubmit(queue, 1, &command_buffer);
|
|
wgpuCommandBufferRelease(command_buffer);
|
|
}
|
|
}
|
|
|
|
static void process_events() {
|
|
ZoneScopedN("process_events");
|
|
|
|
SDL_Event event;
|
|
while (SDL_PollEvent(&event)) {
|
|
switch (event.type) {
|
|
case SDL_EVENT_QUIT: {
|
|
Running = false;
|
|
} break;
|
|
|
|
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: {
|
|
window_size.x = event.window.data1;
|
|
window_size.y = event.window.data2;
|
|
} break;
|
|
}
|
|
|
|
if (in_editor) {
|
|
process_event_editor(event);
|
|
} else {
|
|
process_event_game(event);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void update_state() {
|
|
ZoneScopedN("update_state");
|
|
|
|
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 (in_editor) {
|
|
update_state_editor();
|
|
} else {
|
|
update_state_game();
|
|
}
|
|
}
|
|
|
|
static void render(WGPUTexture surface_texture) {
|
|
ZoneScopedN("render");
|
|
|
|
u32vec2 surface_size = { wgpuTextureGetWidth(surface_texture), wgpuTextureGetHeight(surface_texture) };
|
|
|
|
if (framebuffer_size.x != surface_size.x || framebuffer_size.x != surface_size.y) {
|
|
if (framebuffer) wgpuTextureRelease(framebuffer);
|
|
|
|
WGPUTextureDescriptor descriptor = {
|
|
.label = { .data = "framebuffer", .length = WGPU_STRLEN },
|
|
.usage = WGPUTextureUsage_RenderAttachment,
|
|
.dimension = WGPUTextureDimension_2D,
|
|
.size = { .width = surface_size.x, .height = surface_size.y, .depthOrArrayLayers = 1 },
|
|
.format = surface_configuration.format,
|
|
.mipLevelCount = 1,
|
|
.sampleCount = 4,
|
|
.viewFormatCount = 0,
|
|
.viewFormats = NULL,
|
|
};
|
|
|
|
framebuffer = wgpuDeviceCreateTexture(device, &descriptor);
|
|
framebuffer_size = surface_size;
|
|
}
|
|
|
|
WGPUTextureView surface_texture_view = wgpuTextureCreateView(surface_texture, NULL);
|
|
WGPUTextureView framebuffer_view = wgpuTextureCreateView(framebuffer, NULL);
|
|
|
|
WGPURenderPassColorAttachment framebuffer_color_attachment = {
|
|
.view = framebuffer_view,
|
|
.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED,
|
|
.resolveTarget = surface_texture_view,
|
|
.loadOp = WGPULoadOp_Clear,
|
|
.storeOp = WGPUStoreOp_Discard,
|
|
.clearValue = { .r = 0.01f, .g = 0.01f, .b = 0.01f, .a = 1.0f },
|
|
};
|
|
|
|
if (in_editor) {
|
|
render_editor(framebuffer_color_attachment);
|
|
} else {
|
|
render_game(framebuffer_color_attachment);
|
|
}
|
|
|
|
wgpuTextureViewRelease(framebuffer_view);
|
|
wgpuTextureViewRelease(surface_texture_view);
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
setup_memory_functions();
|
|
setup_working_directory();
|
|
|
|
#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()) {
|
|
log_error("Failed to init SDL_mixer. Exiting.");
|
|
return 1;
|
|
}
|
|
|
|
window = SDL_CreateWindow("Mikemon", window_size.x, window_size.y, SDL_WINDOW_RESIZABLE);
|
|
if (!window) {
|
|
log_error("Failed to create window (%s). Exiting.", SDL_GetError());
|
|
return 1;
|
|
}
|
|
|
|
if (!init_webgpu()) {
|
|
log_error("Failed to initialize webgpu. Exiting.");
|
|
return 1;
|
|
}
|
|
|
|
if (!load_map("map.sv", ¤t_map)) {
|
|
log_error("Failed to load initial map. Exiting.");
|
|
return 1;
|
|
}
|
|
|
|
mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, NULL);
|
|
if (!mixer) {
|
|
log_error("Failed to open default audio device. Ignoring.");
|
|
}
|
|
|
|
music_track = MIX_CreateTrack(mixer);
|
|
if (!music_track) {
|
|
log_error("Failed to create music track. Ignoring.");
|
|
}
|
|
|
|
sfx_track = MIX_CreateTrack(mixer);
|
|
if (!sfx_track) {
|
|
log_error("Failed to create sfx track. Ignoring.");
|
|
}
|
|
|
|
music_setting_off_piano = MIX_LoadAudio(mixer, ASSETS_PATH "music/setting_off_piano.opus", false);
|
|
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;
|
|
|
|
io.Fonts->AddFontDefaultVector();
|
|
|
|
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, "Settings") == 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) {
|
|
if (strncmp(line, "num", 3) == 0) {
|
|
SDL_sscanf(line, "num=%d", &num_used_tint_times);
|
|
} else if (strncmp(line, "enable", 6) == 0) {
|
|
Uint32 enable = 0;
|
|
SDL_sscanf(line, "enable=%d", &enable);
|
|
enable_time_tints = !!enable;
|
|
}
|
|
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][Settings]\n");
|
|
buffer->appendf("enable=%u\n", enable_time_tints ? 1 : 0);
|
|
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_SetMixerGain(mixer, volume_master / 100.0f);
|
|
}
|
|
if (SDL_sscanf(line, "Music=%f", &volume_music) == 1)
|
|
MIX_SetTrackGain(music_track, volume_music / 100.0f);
|
|
if (SDL_sscanf(line, "SFX=%f", &volume_sfx) == 1)
|
|
MIX_SetTrackGain(sfx_track, 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);
|
|
|
|
SDL_GetWindowSizeInPixels(window, &window_size.x, &window_size.y);
|
|
|
|
while (Running) {
|
|
ZoneScopedN("main_loop");
|
|
|
|
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);
|
|
|
|
process_events();
|
|
|
|
i32vec2 surface_size = { wgpuTextureGetWidth(surface_texture.texture), wgpuTextureGetHeight(surface_texture.texture) };
|
|
if (surface_size != window_size) {
|
|
wgpuTextureViewRelease(surface_texture_view);
|
|
wgpuTextureRelease(surface_texture.texture);
|
|
|
|
surface_configuration.width = window_size.x;
|
|
surface_configuration.height = window_size.y;
|
|
wgpuSurfaceConfigure(surface, &surface_configuration);
|
|
continue;
|
|
}
|
|
|
|
update_state();
|
|
render(surface_texture.texture);
|
|
|
|
{
|
|
ZoneScopedN("wgpuSurfacePresent");
|
|
wgpuSurfacePresent(surface);
|
|
wgpuTextureRelease(surface_texture.texture);
|
|
}
|
|
|
|
FrameMark;
|
|
}
|
|
|
|
ImGui_ImplWGPU_Shutdown();
|
|
ImGui_ImplSDL3_Shutdown();
|
|
ImGui::DestroyContext();
|
|
|
|
return 0;
|
|
}
|