#include "renderer.h"
#include <dcimgui_impl_sdlgpu3.h>

static bool enable_debug_mode = false;

typedef struct R_Buffer_Impl {
    SDL_GPUBuffer *buffer;
    void *cpu_uniform_data;

    Uint32 num_bytes;
    R_Buffer_Usage usage;
} R_Buffer_Impl;

typedef struct R_Texture_Impl {
    SDL_GPUTexture *texture;

    Uint32 width;
    Uint32 height;

    bool multisampled;
    R_Texture_Format format;
} R_Texture_Impl;

R_Texture R_surface;

static SDL_Window    *window;
static SDL_GPUDevice *device;

static R_Texture_Impl surface;

static SDL_GPUTransferBuffer *transfer_buffer;

static SDL_GPUCommandBuffer *copy_command_buffer;
static SDL_GPUCopyPass      *copy_pass;

static SDL_GPUCommandBuffer *render_command_buffer;
static SDL_GPURenderPass    *render_pass;

static SDL_GPUSampler *bilinear_sampler;

static SDL_GPUGraphicsPipeline *shaders[R_SHADER_COUNT];

static SDL_GPUTextureFormat texture_formats[] = {
    [R_TEXTURE_FORMAT_R8_UNORM]         = SDL_GPU_TEXTUREFORMAT_R8_UNORM,
    [R_TEXTURE_FORMAT_R16_UINT]         = SDL_GPU_TEXTUREFORMAT_R16_UINT,
    [R_TEXTURE_FORMAT_RGBA8_UNORM]      = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM,
    [R_TEXTURE_FORMAT_RGBA8_UNORM_SRGB] = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB,
    [R_TEXTURE_FORMAT_BGRA8_UNORM]      = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM,
    [R_TEXTURE_FORMAT_BGRA8_UNORM_SRGB] = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB,
};
static_assert(SDL_arraysize(texture_formats) == R_TEXTURE_FORMAT_COUNT);

static Uint32 texel_sizes[] = {
    [R_TEXTURE_FORMAT_R8_UNORM]         = 1,
    [R_TEXTURE_FORMAT_R16_UINT]         = 2,
    [R_TEXTURE_FORMAT_RGBA8_UNORM]      = 4,
    [R_TEXTURE_FORMAT_RGBA8_UNORM_SRGB] = 4,
    [R_TEXTURE_FORMAT_BGRA8_UNORM]      = 4,
    [R_TEXTURE_FORMAT_BGRA8_UNORM_SRGB] = 4,
};
static_assert(SDL_arraysize(texel_sizes) == R_TEXTURE_FORMAT_COUNT);

static SDL_GPUTextureUsageFlags texture_usages[] = {
    [R_TEXTURE_USAGE_SAMPLED] = SDL_GPU_TEXTUREUSAGE_SAMPLER,
    [R_TEXTURE_USAGE_STORAGE] = SDL_GPU_TEXTUREUSAGE_GRAPHICS_STORAGE_READ,
    [R_TEXTURE_USAGE_TARGET]  = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET,
};
static_assert(SDL_arraysize(texture_usages) == R_TEXTURE_USAGE_COUNT);

static SDL_GPUBufferUsageFlags buffer_usages[] = {
    [R_BUFFER_USAGE_UNIFORM] = 0,
    [R_BUFFER_USAGE_STORAGE] = SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ,
    [R_BUFFER_USAGE_VERTEX]  = SDL_GPU_BUFFERUSAGE_VERTEX,
    [R_BUFFER_USAGE_INDEX]   = SDL_GPU_BUFFERUSAGE_INDEX,
};
static_assert(SDL_arraysize(buffer_usages) == R_BUFFER_USAGE_COUNT);

static SDL_GPULoadOp load_ops[] = {
    [R_LOAD_OP_LOAD]  = SDL_GPU_LOADOP_LOAD,
    [R_LOAD_OP_CLEAR] = SDL_GPU_LOADOP_CLEAR,
};
static_assert(SDL_arraysize(load_ops) == R_LOAD_OP_COUNT);

static SDL_GPUStoreOp store_ops[] = {
    [R_STORE_OP_STORE]   = SDL_GPU_STOREOP_STORE,
    [R_STORE_OP_DISCARD] = SDL_GPU_STOREOP_DONT_CARE,
};
static_assert(SDL_arraysize(store_ops) == R_STORE_OP_COUNT);

R_Texture renderer_texture_create(R_Texture_Format format, R_Texture_Usage usage, bool multisampled, Uint32 width, Uint32 height, void *data, const char *debug_name) {
    SDL_PropertiesID properties = SDL_CreateProperties();
    SDL_SetStringProperty(properties, SDL_PROP_GPU_TEXTURE_CREATE_NAME_STRING, debug_name);
    
    SDL_GPUTexture *texture = SDL_CreateGPUTexture(device, &(SDL_GPUTextureCreateInfo){
        .type                 = SDL_GPU_TEXTURETYPE_2D,
        .format               = texture_formats[format],
        .usage                = texture_usages[usage],
        .width                = width,
        .height               = height,
        .layer_count_or_depth = 1,
        .num_levels           = 1,
        .sample_count         = multisampled ? SDL_GPU_SAMPLECOUNT_8 : SDL_GPU_SAMPLECOUNT_1,
        .props                = properties,
    });
    SDL_DestroyProperties(properties);
    if (!texture) return NULL;

    R_Texture_Impl *result = SDL_calloc(1, sizeof(R_Texture_Impl));
    if (!result) {
        SDL_ReleaseGPUTexture(device, texture);
        return NULL;
    }

    *result = (R_Texture_Impl){
        .texture = texture,

        .width  = width,
        .height = height,

        .multisampled = multisampled,
        .format       = format,
    };

    if (data) {
        renderer_texture_update(result, 0, 0, width, height, data, width * texel_sizes[format]);
    }

    return result;
}

void renderer_texture_update(R_Texture texture, Uint32 offset_x, Uint32 offset_y, Uint32 width, Uint32 height, void *data, Uint32 bytes_per_row) {
    void *gpu_mem = SDL_MapGPUTransferBuffer(device, transfer_buffer, true);
    SDL_memcpy(gpu_mem, data, bytes_per_row * height);
    SDL_UnmapGPUTransferBuffer(device, transfer_buffer);

    SDL_UploadToGPUTexture(copy_pass, 
        &(SDL_GPUTextureTransferInfo){
            .transfer_buffer = transfer_buffer,
            .offset          = 0,
            .pixels_per_row  = width,
            .rows_per_layer  = height,
        },
        &(SDL_GPUTextureRegion){
            .texture   = texture->texture,
            .mip_level = 0,
            .layer     = 0,

            .x = offset_x,
            .y = offset_y,
            .z = 0,

            .w = width,
            .h = height,
            .d = 1,
        },
        false
    );
}

void renderer_texture_destroy(R_Texture texture) {
    SDL_ReleaseGPUTexture(device, texture->texture);
    SDL_free(texture);
}

Uint32 renderer_texture_get_width(R_Texture texture) {
    return texture->width;
}

Uint32 renderer_texture_get_height(R_Texture texture) {
    return texture->height;
}

bool renderer_texture_get_multisampled(R_Texture texture) {
    return texture->multisampled;
}

R_Texture_Format renderer_texture_get_format(R_Texture texture) {
    return texture->format;
}

R_Buffer renderer_buffer_create(R_Buffer_Usage usage, Uint32 num_bytes, void *data, const char *debug_name) {
    R_Buffer_Impl *result = SDL_calloc(1, sizeof(R_Buffer_Impl));
    if (!result) return NULL;

    if (usage == R_BUFFER_USAGE_UNIFORM) {
        *result = (R_Buffer_Impl){
            .cpu_uniform_data = SDL_calloc(1, num_bytes),
            
            .num_bytes = num_bytes,
            .usage     = usage,
        };

        if (!result->cpu_uniform_data) {
            SDL_free(result);
            return NULL;
        }

        if (data) {
            renderer_buffer_update(result, 0, num_bytes, data);
        }
    } else {
        SDL_PropertiesID properties = SDL_CreateProperties();
        SDL_SetStringProperty(properties, SDL_PROP_GPU_BUFFER_CREATE_NAME_STRING, debug_name);
        
        SDL_GPUBuffer *buffer = SDL_CreateGPUBuffer(device, &(SDL_GPUBufferCreateInfo){
            .usage = buffer_usages[usage],
            .size  = num_bytes,
            .props = properties,
        });
        SDL_DestroyProperties(properties);
        if (!buffer)  {
            SDL_free(result);
            return NULL;
        }

        *result = (R_Buffer_Impl){
            .buffer = buffer,
            
            .num_bytes = num_bytes,
            .usage     = usage,
        };

        if (data) {
            renderer_buffer_update(result, 0, num_bytes, data);
        }
    }

    return result;
}

void renderer_buffer_update(R_Buffer buffer, Uint32 offset, Uint32 num_bytes, void *data) {
    SDL_assert(offset + num_bytes <= buffer->num_bytes);

    if (buffer->usage == R_BUFFER_USAGE_UNIFORM) {
        memcpy(buffer->cpu_uniform_data, data + offset, num_bytes);
    } else {
        void *gpu_mem = SDL_MapGPUTransferBuffer(device, transfer_buffer, true);
        SDL_memcpy(gpu_mem, data, num_bytes);
        SDL_UnmapGPUTransferBuffer(device, transfer_buffer);

        SDL_UploadToGPUBuffer(copy_pass,
            &(SDL_GPUTransferBufferLocation) {
                .transfer_buffer = transfer_buffer,
                .offset          = 0,
            }, 
            &(SDL_GPUBufferRegion){
                .buffer = buffer->buffer,
                .offset = offset,
                .size   = num_bytes,
            },
            false
        );
    }
}

void renderer_buffer_destroy(R_Buffer buffer) {
    if (buffer->buffer) SDL_ReleaseGPUBuffer(device, buffer->buffer);
    if (buffer->cpu_uniform_data) SDL_free(buffer->cpu_uniform_data);
    SDL_free(buffer);
}

Uint32 renderer_buffer_get_size(R_Buffer buffer) {
    return buffer->num_bytes;
}

R_Buffer_Usage renderer_buffer_get_usage(R_Buffer buffer){
    return buffer->usage;
}

void renderer_frame_begin() {
    SDL_assert(R_surface == NULL);

    Uint32 surface_width = 0, surface_height = 0;
    SDL_WaitAndAcquireGPUSwapchainTexture(render_command_buffer, window, &surface.texture, &surface.width, &surface.height);

    R_surface = &surface;
}

void renderer_frame_end() {
    SDL_EndGPUCopyPass(copy_pass);
    SDL_SubmitGPUCommandBuffer(copy_command_buffer);

    if (render_pass) {
        SDL_EndGPURenderPass(render_pass);
        render_pass = NULL;
    }

    SDL_GPUFence *frame_fence = SDL_SubmitGPUCommandBufferAndAcquireFence(render_command_buffer);
    SDL_WaitForGPUFences(device, true, &frame_fence, 1);
    SDL_ReleaseGPUFence(device, frame_fence);

    copy_command_buffer   = SDL_AcquireGPUCommandBuffer(device);
    render_command_buffer = SDL_AcquireGPUCommandBuffer(device);

    R_surface = NULL;

    copy_pass = SDL_BeginGPUCopyPass(copy_command_buffer);
}

void renderer_frame_set_target(R_Texture target, R_Texture resolve_target, R_Load_Op load_op, R_Store_Op store_op, R_Color clear_color) {
    if (render_pass) {
        SDL_EndGPURenderPass(render_pass);
        render_pass = NULL;
    }

    render_pass = SDL_BeginGPURenderPass(render_command_buffer, 
        &(SDL_GPUColorTargetInfo){
            .texture              = target->texture,
            .mip_level            = 0,
            .layer_or_depth_plane = 0,

            .clear_color = { clear_color.r, clear_color.g, clear_color.b, clear_color.a },
            .load_op     = load_ops[load_op],
            .store_op    = resolve_target ? (store_op == R_STORE_OP_STORE ? SDL_GPU_STOREOP_RESOLVE_AND_STORE : SDL_GPU_STOREOP_RESOLVE) : store_ops[store_op],

            .resolve_texture   = resolve_target ? resolve_target->texture : NULL,
            .resolve_mip_level = 0,
            .resolve_layer     = 0,

            .cycle                 = false,
            .cycle_resolve_texture = false,
        },
        1,
        NULL
    );
}

void renderer_frame_set_shader(R_Shader shader) {
    SDL_BindGPUGraphicsPipeline(render_pass, shaders[shader]);
}

void renderer_frame_draw(R_Buffer vertex_buffer, R_Buffer index_buffer, R_Buffer instance_buffer, Uint32 num_indices, Uint32 num_instances, const R_Draw_Resources *resources) {
    SDL_assert(render_pass);


    for (int i = 0; i < resources->num_vertex_sampled_textures; i++) SDL_BindGPUVertexSamplers(render_pass, i, &(SDL_GPUTextureSamplerBinding){ .texture = resources->vertex_sampled_textures[i]->texture, .sampler = bilinear_sampler }, 1);
    for (int i = 0; i < resources->num_vertex_storage_textures; i++) SDL_BindGPUVertexStorageTextures(render_pass, i, &resources->vertex_storage_textures[i]->texture, 1);
    for (int i = 0; i < resources->num_vertex_storage_buffers;  i++) SDL_BindGPUVertexStorageBuffers(render_pass, i, &resources->vertex_storage_buffers[i]->buffer, 1);
    for (int i = 0; i < resources->num_vertex_uniform_buffers;  i++) SDL_PushGPUVertexUniformData(render_command_buffer, i, resources->vertex_uniform_buffers[i]->cpu_uniform_data, resources->vertex_uniform_buffers[i]->num_bytes);

    for (int i = 0; i < resources->num_fragment_sampled_textures; i++) SDL_BindGPUFragmentSamplers(render_pass, i, &(SDL_GPUTextureSamplerBinding){ .texture = resources->fragment_sampled_textures[i]->texture, .sampler = bilinear_sampler }, 1);
    for (int i = 0; i < resources->num_fragment_storage_textures; i++) SDL_BindGPUFragmentStorageTextures(render_pass, i, &resources->fragment_storage_textures[i]->texture, 1);
    for (int i = 0; i < resources->num_fragment_storage_buffers;  i++) SDL_BindGPUFragmentStorageBuffers(render_pass, i, &resources->fragment_storage_buffers[i]->buffer, 1);
    for (int i = 0; i < resources->num_fragment_uniform_buffers;  i++) SDL_PushGPUFragmentUniformData(render_command_buffer, i, resources->fragment_uniform_buffers[i]->cpu_uniform_data, resources->fragment_uniform_buffers[i]->num_bytes);

    if (vertex_buffer)   SDL_BindGPUVertexBuffers(render_pass,                     0, &(SDL_GPUBufferBinding){ .buffer = vertex_buffer->buffer,   .offset = 0 }, 1);
    if (instance_buffer) SDL_BindGPUVertexBuffers(render_pass, vertex_buffer ? 1 : 0, &(SDL_GPUBufferBinding){ .buffer = instance_buffer->buffer, .offset = 0 }, 1);

    if (index_buffer) {
        SDL_BindGPUIndexBuffer(render_pass, &(SDL_GPUBufferBinding){ .buffer = index_buffer->buffer, .offset = 0 }, SDL_GPU_INDEXELEMENTSIZE_16BIT);
        SDL_DrawGPUIndexedPrimitives(render_pass, num_indices, num_instances, 0, 0, 0);
    } else {
        SDL_DrawGPUPrimitives(render_pass, num_indices, num_instances, 0, 0);
    }
}

static R_Texture_Format R_Texture_Format_from_SDL_GPUTextureFormat(SDL_GPUTextureFormat format) {
    switch (format) {
        case SDL_GPU_TEXTUREFORMAT_R8_UNORM:            return R_TEXTURE_FORMAT_R8_UNORM;
        case SDL_GPU_TEXTUREFORMAT_R16_UINT:            return R_TEXTURE_FORMAT_R16_UINT;
        case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM:      return R_TEXTURE_FORMAT_RGBA8_UNORM;
        case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB: return R_TEXTURE_FORMAT_RGBA8_UNORM_SRGB;
        case SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM:      return R_TEXTURE_FORMAT_BGRA8_UNORM;
        case SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB: return R_TEXTURE_FORMAT_BGRA8_UNORM_SRGB;

        default: {
            SDL_assert_always(false);
            return R_TEXTURE_FORMAT_BGRA8_UNORM_SRGB;
        };
    };
}

bool renderer_init(SDL_Window *window_) {
    window = window_;

    device = SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV, enable_debug_mode, NULL);
    if (!device) {
        SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to create gpu device.");
        return false;
    }

    if (!SDL_ClaimWindowForGPUDevice(device, window)) {
        SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to claim window for gpu device.");
        SDL_DestroyGPUDevice(device);
        return false;
    }

    SDL_SetGPUSwapchainParameters(device, window, SDL_GPU_SWAPCHAINCOMPOSITION_SDR_LINEAR, SDL_GPU_PRESENTMODE_VSYNC);

    surface.format = R_Texture_Format_from_SDL_GPUTextureFormat(SDL_GetGPUSwapchainTextureFormat(device, window));

    bilinear_sampler = SDL_CreateGPUSampler(device, &(SDL_GPUSamplerCreateInfo){
        .min_filter  = SDL_GPU_FILTER_LINEAR,
        .mag_filter  = SDL_GPU_FILTER_LINEAR,
        .mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST,
    
        .address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
        .address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
        .address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
    
        .mip_lod_bias   = 0.0f,
        .max_anisotropy = 1.0f,
        .compare_op     = SDL_GPU_COMPAREOP_INVALID,

        .min_lod = 0.0f,
        .max_lod = 0.0f,

        .enable_anisotropy = false,
        .enable_compare    = false,
    });

    // Shaders
    { // Character
        const Uint8 shader_source[] = {
            #embed "shaders/spirv/character.spv"
        };

        SDL_GPUShader *vertex_shader = SDL_CreateGPUShader(device, &(SDL_GPUShaderCreateInfo){
            .code_size  = sizeof(shader_source),
            .code       = shader_source,
            .entrypoint = "main_vertex",

            .format = SDL_GPU_SHADERFORMAT_SPIRV,
            .stage  = SDL_GPU_SHADERSTAGE_VERTEX,

            .num_samplers         = 0,
            .num_storage_textures = 0,
            .num_storage_buffers  = 0,
            .num_uniform_buffers  = 1,
        });

        SDL_GPUShader *fragment_shader = SDL_CreateGPUShader(device, &(SDL_GPUShaderCreateInfo){
            .code_size  = sizeof(shader_source),
            .code       = shader_source,
            .entrypoint = "main_fragment",

            .format = SDL_GPU_SHADERFORMAT_SPIRV,
            .stage  = SDL_GPU_SHADERSTAGE_FRAGMENT,

            .num_samplers         = 1,
            .num_storage_textures = 0,
            .num_storage_buffers  = 0,
            .num_uniform_buffers  = 1,
        });

        SDL_PropertiesID properties = SDL_CreateProperties();
        SDL_SetStringProperty(properties, SDL_PROP_GPU_BUFFER_CREATE_NAME_STRING, "R_SHADER character");

        shaders[R_SHADER_CHARACTER] = SDL_CreateGPUGraphicsPipeline(device, &(SDL_GPUGraphicsPipelineCreateInfo){
            .vertex_shader   = vertex_shader,
            .fragment_shader = fragment_shader,

            .vertex_input_state = {
                .vertex_buffer_descriptions = (SDL_GPUVertexBufferDescription[]){
                    {
                        .slot       = 0,
                        .pitch      = 12,
                        .input_rate = SDL_GPU_VERTEXINPUTRATE_INSTANCE,

                        .instance_step_rate = 0,
                    },
                },
                .num_vertex_buffers = 1,

                .vertex_attributes = (SDL_GPUVertexAttribute[]){
                    {
                        .location    = 2,
                        .buffer_slot = 0,
                        .format      = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2,
                        .offset      = 0,
                    },
                    {
                        .location    = 3,
                        .buffer_slot = 0,
                        .format      = SDL_GPU_VERTEXELEMENTFORMAT_UINT,
                        .offset      = 8,
                    },
                },
                .num_vertex_attributes = 2,
            },

            .primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,

            .rasterizer_state = {
                .fill_mode  = SDL_GPU_FILLMODE_FILL,
                .cull_mode  = SDL_GPU_CULLMODE_BACK,
                .front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE,

                .depth_bias_constant_factor = 0.0f,
                .depth_bias_clamp           = 0.0f,
                .depth_bias_slope_factor    = 0.0f,

                .enable_depth_bias = false,
                .enable_depth_clip = false,
            },

            .multisample_state = {
                .sample_count = SDL_GPU_SAMPLECOUNT_8,
                .sample_mask  = 0,
                .enable_mask  = 0,

                .enable_alpha_to_coverage = false,
            },

            .depth_stencil_state = {
                .compare_op = SDL_GPU_COMPAREOP_GREATER_OR_EQUAL,

                .back_stencil_state  = { .fail_op = SDL_GPU_STENCILOP_INVALID, .pass_op = SDL_GPU_STENCILOP_INVALID, .depth_fail_op = SDL_GPU_STENCILOP_INVALID, .compare_op = SDL_GPU_COMPAREOP_INVALID, },
                .front_stencil_state = { .fail_op = SDL_GPU_STENCILOP_INVALID, .pass_op = SDL_GPU_STENCILOP_INVALID, .depth_fail_op = SDL_GPU_STENCILOP_INVALID, .compare_op = SDL_GPU_COMPAREOP_INVALID, },

                .compare_mask = 0,
                .write_mask   = 0,
        
                .enable_depth_test   = false,
                .enable_depth_write  = false,
                .enable_stencil_test = false,
            },

            .target_info = {
                .color_target_descriptions = (SDL_GPUColorTargetDescription[]){
                    {
                        .format      = texture_formats[surface.format],
                        .blend_state = {
                            .src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
                            .dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
                            .color_blend_op        = SDL_GPU_BLENDOP_ADD,

                            .src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
                            .dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
                            .alpha_blend_op        = SDL_GPU_BLENDOP_ADD,

                            .color_write_mask = 0,

                            .enable_blend            = true,
                            .enable_color_write_mask = false,
                        },
                    }
                },

                .num_color_targets = 1,

                .depth_stencil_format     = SDL_GPU_TEXTUREFORMAT_INVALID,
                .has_depth_stencil_target = false,
            },
        });
        SDL_DestroyProperties(properties);
        SDL_ReleaseGPUShader(device, vertex_shader);
        SDL_ReleaseGPUShader(device, fragment_shader);
    }

    { // Character Shadow
        const Uint8 shader_source[] = {
            #embed "shaders/spirv/character_shadow.spv"
        };

        SDL_GPUShader *vertex_shader = SDL_CreateGPUShader(device, &(SDL_GPUShaderCreateInfo){
            .code_size  = sizeof(shader_source),
            .code       = shader_source,
            .entrypoint = "main_vertex",

            .format = SDL_GPU_SHADERFORMAT_SPIRV,
            .stage  = SDL_GPU_SHADERSTAGE_VERTEX,

            .num_samplers         = 0,
            .num_storage_textures = 0,
            .num_storage_buffers  = 0,
            .num_uniform_buffers  = 1,
        });

        SDL_GPUShader *fragment_shader = SDL_CreateGPUShader(device, &(SDL_GPUShaderCreateInfo){
            .code_size  = sizeof(shader_source),
            .code       = shader_source,
            .entrypoint = "main_fragment",

            .format = SDL_GPU_SHADERFORMAT_SPIRV,
            .stage  = SDL_GPU_SHADERSTAGE_FRAGMENT,

            .num_samplers         = 1,
            .num_storage_textures = 0,
            .num_storage_buffers  = 0,
            .num_uniform_buffers  = 1,
        });

        SDL_PropertiesID properties = SDL_CreateProperties();
        SDL_SetStringProperty(properties, SDL_PROP_GPU_BUFFER_CREATE_NAME_STRING, "R_SHADER character_shadow");

        shaders[R_SHADER_CHARACTER_SHADOW] = SDL_CreateGPUGraphicsPipeline(device, &(SDL_GPUGraphicsPipelineCreateInfo){
            .vertex_shader   = vertex_shader,
            .fragment_shader = fragment_shader,

            .vertex_input_state = {
                .vertex_buffer_descriptions = (SDL_GPUVertexBufferDescription[]){
                    {
                        .slot       = 0,
                        .pitch      = 12,
                        .input_rate = SDL_GPU_VERTEXINPUTRATE_INSTANCE,

                        .instance_step_rate = 0,
                    },
                },
                .num_vertex_buffers = 1,

                .vertex_attributes = (SDL_GPUVertexAttribute[]){
                    {
                        .location    = 2,
                        .buffer_slot = 0,
                        .format      = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2,
                        .offset      = 0,
                    },
                    {
                        .location    = 3,
                        .buffer_slot = 0,
                        .format      = SDL_GPU_VERTEXELEMENTFORMAT_UINT,
                        .offset      = 8,
                    },
                },
                .num_vertex_attributes = 2,
            },

            .primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,

            .rasterizer_state = {
                .fill_mode  = SDL_GPU_FILLMODE_FILL,
                .cull_mode  = SDL_GPU_CULLMODE_BACK,
                .front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE,

                .depth_bias_constant_factor = 0.0f,
                .depth_bias_clamp           = 0.0f,
                .depth_bias_slope_factor    = 0.0f,

                .enable_depth_bias = false,
                .enable_depth_clip = false,
            },

            .multisample_state = {
                .sample_count = SDL_GPU_SAMPLECOUNT_8,
                .sample_mask  = 0,
                .enable_mask  = 0,

                .enable_alpha_to_coverage = false,
            },

            .depth_stencil_state = {
                .compare_op = SDL_GPU_COMPAREOP_GREATER_OR_EQUAL,

                .back_stencil_state  = { .fail_op = SDL_GPU_STENCILOP_INVALID, .pass_op = SDL_GPU_STENCILOP_INVALID, .depth_fail_op = SDL_GPU_STENCILOP_INVALID, .compare_op = SDL_GPU_COMPAREOP_INVALID, },
                .front_stencil_state = { .fail_op = SDL_GPU_STENCILOP_INVALID, .pass_op = SDL_GPU_STENCILOP_INVALID, .depth_fail_op = SDL_GPU_STENCILOP_INVALID, .compare_op = SDL_GPU_COMPAREOP_INVALID, },

                .compare_mask = 0,
                .write_mask   = 0,
        
                .enable_depth_test   = false,
                .enable_depth_write  = false,
                .enable_stencil_test = false,
            },

            .target_info = {
                .color_target_descriptions = (SDL_GPUColorTargetDescription[]){
                    {
                        .format      = texture_formats[surface.format],
                        .blend_state = {
                            .src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
                            .dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
                            .color_blend_op        = SDL_GPU_BLENDOP_ADD,

                            .src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
                            .dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
                            .alpha_blend_op        = SDL_GPU_BLENDOP_ADD,

                            .color_write_mask = 0,

                            .enable_blend            = true,
                            .enable_color_write_mask = false,
                        },
                    }
                },

                .num_color_targets = 1,

                .depth_stencil_format     = SDL_GPU_TEXTUREFORMAT_INVALID,
                .has_depth_stencil_target = false,
            },
        });
        SDL_DestroyProperties(properties);
        SDL_ReleaseGPUShader(device, vertex_shader);
        SDL_ReleaseGPUShader(device, fragment_shader);
    }

    { // World
        const Uint8 shader_source[] = {
            #embed "shaders/spirv/world.spv"
        };

        SDL_GPUShader *vertex_shader = SDL_CreateGPUShader(device, &(SDL_GPUShaderCreateInfo){
            .code_size  = sizeof(shader_source),
            .code       = shader_source,
            .entrypoint = "main_vertex",

            .format = SDL_GPU_SHADERFORMAT_SPIRV,
            .stage  = SDL_GPU_SHADERSTAGE_VERTEX,

            .num_samplers         = 0,
            .num_storage_textures = 1,
            .num_storage_buffers  = 0,
            .num_uniform_buffers  = 2,
        });

        SDL_GPUShader *fragment_shader = SDL_CreateGPUShader(device, &(SDL_GPUShaderCreateInfo){
            .code_size  = sizeof(shader_source),
            .code       = shader_source,
            .entrypoint = "main_fragment",

            .format = SDL_GPU_SHADERFORMAT_SPIRV,
            .stage  = SDL_GPU_SHADERSTAGE_FRAGMENT,

            .num_samplers         = 1,
            .num_storage_textures = 0,
            .num_storage_buffers  = 1,
            .num_uniform_buffers  = 1,
        });

        SDL_PropertiesID properties = SDL_CreateProperties();
        SDL_SetStringProperty(properties, SDL_PROP_GPU_BUFFER_CREATE_NAME_STRING, "R_SHADER world");

        shaders[R_SHADER_WORLD] = SDL_CreateGPUGraphicsPipeline(device, &(SDL_GPUGraphicsPipelineCreateInfo){
            .vertex_shader   = vertex_shader,
            .fragment_shader = fragment_shader,

            .vertex_input_state = {
                .vertex_buffer_descriptions = (SDL_GPUVertexBufferDescription[]) {},
                .num_vertex_buffers = 0,

                .vertex_attributes = (SDL_GPUVertexAttribute[]) {},
                .num_vertex_attributes = 0,
            },

            .primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,

            .rasterizer_state = {
                .fill_mode  = SDL_GPU_FILLMODE_FILL,
                .cull_mode  = SDL_GPU_CULLMODE_BACK,
                .front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE,

                .depth_bias_constant_factor = 0.0f,
                .depth_bias_clamp           = 0.0f,
                .depth_bias_slope_factor    = 0.0f,

                .enable_depth_bias = false,
                .enable_depth_clip = false,
            },

            .multisample_state = {
                .sample_count = SDL_GPU_SAMPLECOUNT_8,
                .sample_mask  = 0,
                .enable_mask  = 0,

                .enable_alpha_to_coverage = false,
            },

            .depth_stencil_state = {
                .compare_op = SDL_GPU_COMPAREOP_GREATER_OR_EQUAL,

                .back_stencil_state  = { .fail_op = SDL_GPU_STENCILOP_INVALID, .pass_op = SDL_GPU_STENCILOP_INVALID, .depth_fail_op = SDL_GPU_STENCILOP_INVALID, .compare_op = SDL_GPU_COMPAREOP_INVALID, },
                .front_stencil_state = { .fail_op = SDL_GPU_STENCILOP_INVALID, .pass_op = SDL_GPU_STENCILOP_INVALID, .depth_fail_op = SDL_GPU_STENCILOP_INVALID, .compare_op = SDL_GPU_COMPAREOP_INVALID, },

                .compare_mask = 0,
                .write_mask   = 0,
        
                .enable_depth_test   = false,
                .enable_depth_write  = false,
                .enable_stencil_test = false,
            },

            .target_info = {
                .color_target_descriptions = (SDL_GPUColorTargetDescription[]){
                    {
                        .format      = texture_formats[surface.format],
                        .blend_state = {
                            .src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
                            .dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
                            .color_blend_op        = SDL_GPU_BLENDOP_ADD,

                            .src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
                            .dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
                            .alpha_blend_op        = SDL_GPU_BLENDOP_ADD,

                            .color_write_mask = 0,

                            .enable_blend            = true,
                            .enable_color_write_mask = false,
                        },
                    }
                },

                .num_color_targets = 1,

                .depth_stencil_format     = SDL_GPU_TEXTUREFORMAT_INVALID,
                .has_depth_stencil_target = false,
            },
        });
        SDL_DestroyProperties(properties);
        SDL_ReleaseGPUShader(device, vertex_shader);
        SDL_ReleaseGPUShader(device, fragment_shader);
    }

    { // Placeable
        const Uint8 shader_source[] = {
            #embed "shaders/spirv/placeable.spv"
        };

        SDL_GPUShader *vertex_shader = SDL_CreateGPUShader(device, &(SDL_GPUShaderCreateInfo){
            .code_size  = sizeof(shader_source),
            .code       = shader_source,
            .entrypoint = "main_vertex",

            .format = SDL_GPU_SHADERFORMAT_SPIRV,
            .stage  = SDL_GPU_SHADERSTAGE_VERTEX,

            .num_samplers         = 0,
            .num_storage_textures = 0,
            .num_storage_buffers  = 1,
            .num_uniform_buffers  = 2,
        });

        SDL_GPUShader *fragment_shader = SDL_CreateGPUShader(device, &(SDL_GPUShaderCreateInfo){
            .code_size  = sizeof(shader_source),
            .code       = shader_source,
            .entrypoint = "main_fragment",

            .format = SDL_GPU_SHADERFORMAT_SPIRV,
            .stage  = SDL_GPU_SHADERSTAGE_FRAGMENT,

            .num_samplers         = 1,
            .num_storage_textures = 0,
            .num_storage_buffers  = 1,
            .num_uniform_buffers  = 1,
        });

        SDL_PropertiesID properties = SDL_CreateProperties();
        SDL_SetStringProperty(properties, SDL_PROP_GPU_BUFFER_CREATE_NAME_STRING, "R_SHADER placeable");

        shaders[R_SHADER_PLACEABLE] = SDL_CreateGPUGraphicsPipeline(device, &(SDL_GPUGraphicsPipelineCreateInfo){
            .vertex_shader   = vertex_shader,
            .fragment_shader = fragment_shader,

            .vertex_input_state = {
                .vertex_buffer_descriptions = (SDL_GPUVertexBufferDescription[]){
                    {
                        .slot       = 0,
                        .pitch      = 12,
                        .input_rate = SDL_GPU_VERTEXINPUTRATE_INSTANCE,

                        .instance_step_rate = 0,
                    },
                },
                .num_vertex_buffers = 1,

                .vertex_attributes = (SDL_GPUVertexAttribute[]){
                    {
                        .location    = 0,
                        .buffer_slot = 0,
                        .format      = SDL_GPU_VERTEXELEMENTFORMAT_UINT2,
                        .offset      = 0,
                    },
                    {
                        .location    = 1,
                        .buffer_slot = 0,
                        .format      = SDL_GPU_VERTEXELEMENTFORMAT_UINT,
                        .offset      = 8,
                    },
                },
                .num_vertex_attributes = 2,
            },

            .primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,

            .rasterizer_state = {
                .fill_mode  = SDL_GPU_FILLMODE_FILL,
                .cull_mode  = SDL_GPU_CULLMODE_BACK,
                .front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE,

                .depth_bias_constant_factor = 0.0f,
                .depth_bias_clamp           = 0.0f,
                .depth_bias_slope_factor    = 0.0f,

                .enable_depth_bias = false,
                .enable_depth_clip = false,
            },

            .multisample_state = {
                .sample_count = SDL_GPU_SAMPLECOUNT_8,
                .sample_mask  = 0,
                .enable_mask  = 0,

                .enable_alpha_to_coverage = false,
            },

            .depth_stencil_state = {
                .compare_op = SDL_GPU_COMPAREOP_GREATER_OR_EQUAL,

                .back_stencil_state  = { .fail_op = SDL_GPU_STENCILOP_INVALID, .pass_op = SDL_GPU_STENCILOP_INVALID, .depth_fail_op = SDL_GPU_STENCILOP_INVALID, .compare_op = SDL_GPU_COMPAREOP_INVALID, },
                .front_stencil_state = { .fail_op = SDL_GPU_STENCILOP_INVALID, .pass_op = SDL_GPU_STENCILOP_INVALID, .depth_fail_op = SDL_GPU_STENCILOP_INVALID, .compare_op = SDL_GPU_COMPAREOP_INVALID, },

                .compare_mask = 0,
                .write_mask   = 0,
        
                .enable_depth_test   = false,
                .enable_depth_write  = false,
                .enable_stencil_test = false,
            },

            .target_info = {
                .color_target_descriptions = (SDL_GPUColorTargetDescription[]){
                    {
                        .format      = texture_formats[surface.format],
                        .blend_state = {
                            .src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
                            .dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
                            .color_blend_op        = SDL_GPU_BLENDOP_ADD,

                            .src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
                            .dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
                            .alpha_blend_op        = SDL_GPU_BLENDOP_ADD,

                            .color_write_mask = 0,

                            .enable_blend            = true,
                            .enable_color_write_mask = false,
                        },
                    }
                },

                .num_color_targets = 1,

                .depth_stencil_format     = SDL_GPU_TEXTUREFORMAT_INVALID,
                .has_depth_stencil_target = false,
            },
        });
        SDL_DestroyProperties(properties);
        SDL_ReleaseGPUShader(device, vertex_shader);
        SDL_ReleaseGPUShader(device, fragment_shader);
    }

    { // Grid
        const Uint8 shader_source[] = {
            #embed "shaders/spirv/grid.spv"
        };

        SDL_GPUShader *vertex_shader = SDL_CreateGPUShader(device, &(SDL_GPUShaderCreateInfo){
            .code_size  = sizeof(shader_source),
            .code       = shader_source,
            .entrypoint = "main_vertex",

            .format = SDL_GPU_SHADERFORMAT_SPIRV,
            .stage  = SDL_GPU_SHADERSTAGE_VERTEX,

            .num_samplers         = 0,
            .num_storage_textures = 0,
            .num_storage_buffers  = 0,
            .num_uniform_buffers  = 2,
        });

        SDL_GPUShader *fragment_shader = SDL_CreateGPUShader(device, &(SDL_GPUShaderCreateInfo){
            .code_size  = sizeof(shader_source),
            .code       = shader_source,
            .entrypoint = "main_fragment",

            .format = SDL_GPU_SHADERFORMAT_SPIRV,
            .stage  = SDL_GPU_SHADERSTAGE_FRAGMENT,

            .num_samplers         = 0,
            .num_storage_textures = 0,
            .num_storage_buffers  = 0,
            .num_uniform_buffers  = 0,
        });

        SDL_PropertiesID properties = SDL_CreateProperties();
        SDL_SetStringProperty(properties, SDL_PROP_GPU_BUFFER_CREATE_NAME_STRING, "R_SHADER grid");

        shaders[R_SHADER_GRID] = SDL_CreateGPUGraphicsPipeline(device, &(SDL_GPUGraphicsPipelineCreateInfo){
            .vertex_shader   = vertex_shader,
            .fragment_shader = fragment_shader,

            .vertex_input_state = {
                .vertex_buffer_descriptions = (SDL_GPUVertexBufferDescription[]){},
                .num_vertex_buffers = 0,

                .vertex_attributes = (SDL_GPUVertexAttribute[]){},
                .num_vertex_attributes = 0,
            },

            .primitive_type = SDL_GPU_PRIMITIVETYPE_LINELIST,

            .rasterizer_state = {
                .fill_mode  = SDL_GPU_FILLMODE_FILL,
                .cull_mode  = SDL_GPU_CULLMODE_BACK,
                .front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE,

                .depth_bias_constant_factor = 0.0f,
                .depth_bias_clamp           = 0.0f,
                .depth_bias_slope_factor    = 0.0f,

                .enable_depth_bias = false,
                .enable_depth_clip = false,
            },

            .multisample_state = {
                .sample_count = SDL_GPU_SAMPLECOUNT_8,
                .sample_mask  = 0,
                .enable_mask  = 0,

                .enable_alpha_to_coverage = false,
            },

            .depth_stencil_state = {
                .compare_op = SDL_GPU_COMPAREOP_GREATER_OR_EQUAL,

                .back_stencil_state  = { .fail_op = SDL_GPU_STENCILOP_INVALID, .pass_op = SDL_GPU_STENCILOP_INVALID, .depth_fail_op = SDL_GPU_STENCILOP_INVALID, .compare_op = SDL_GPU_COMPAREOP_INVALID, },
                .front_stencil_state = { .fail_op = SDL_GPU_STENCILOP_INVALID, .pass_op = SDL_GPU_STENCILOP_INVALID, .depth_fail_op = SDL_GPU_STENCILOP_INVALID, .compare_op = SDL_GPU_COMPAREOP_INVALID, },

                .compare_mask = 0,
                .write_mask   = 0,
        
                .enable_depth_test   = false,
                .enable_depth_write  = false,
                .enable_stencil_test = false,
            },

            .target_info = {
                .color_target_descriptions = (SDL_GPUColorTargetDescription[]){
                    {
                        .format      = texture_formats[surface.format],
                        .blend_state = {
                            .src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
                            .dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
                            .color_blend_op        = SDL_GPU_BLENDOP_ADD,

                            .src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
                            .dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
                            .alpha_blend_op        = SDL_GPU_BLENDOP_ADD,

                            .color_write_mask = 0,

                            .enable_blend            = true,
                            .enable_color_write_mask = false,
                        },
                    }
                },

                .num_color_targets = 1,

                .depth_stencil_format     = SDL_GPU_TEXTUREFORMAT_INVALID,
                .has_depth_stencil_target = false,
            },
        });
        SDL_DestroyProperties(properties);
        SDL_ReleaseGPUShader(device, vertex_shader);
        SDL_ReleaseGPUShader(device, fragment_shader);
    }

    for (int i = 0; i < R_SHADER_COUNT; i++) {
        SDL_assert(shaders[i]);
    }

    transfer_buffer = SDL_CreateGPUTransferBuffer(device, &(SDL_GPUTransferBufferCreateInfo){
        .usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
        .size  = 512 * 1024,
    });

    copy_command_buffer   = SDL_AcquireGPUCommandBuffer(device);
    render_command_buffer = SDL_AcquireGPUCommandBuffer(device);

    copy_pass = SDL_BeginGPUCopyPass(copy_command_buffer);

    return true;
}

void ImGui_ImplRenderer_Init() {
    cImGui_ImplSDLGPU3_Init(&(ImGui_ImplSDLGPU3_InitInfo){
        .Device               = device,
        .ColorTargetFormat    = texture_formats[surface.format],
        .MSAASamples          = SDL_GPU_SAMPLECOUNT_8,
        .SwapchainComposition = SDL_GPU_SWAPCHAINCOMPOSITION_SDR,
        .PresentMode          = SDL_GPU_PRESENTMODE_VSYNC,
    });
}

void ImGui_ImplRenderer_NewFrame() {
    cImGui_ImplSDLGPU3_NewFrame();
}

void ImGui_ImplRenderer_Shutdown() {
    cImGui_ImplSDLGPU3_Shutdown();
}

void ImGui_ImplRenderer_RenderDrawData(void* draw_data) {
    SDL_EndGPUCopyPass(copy_pass);
    cImGui_ImplSDLGPU3_PrepareDrawData(draw_data, copy_command_buffer);
    copy_pass = SDL_BeginGPUCopyPass(copy_command_buffer);

    cImGui_ImplSDLGPU3_RenderDrawData(draw_data, render_command_buffer, render_pass);
}

Uint64 ImGui_ImplRenderer_GetTextureID(R_Texture texture) {
    return (Uint64)texture->texture;
}
