/******************************************************************************
*
* Copyright (C) 2026  Brian Berlin
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
#ifndef shader_gpu_h
#define shader_gpu_h

#include <stddef.h>

#include <SDL3/SDL.h>
#include <SDL3/SDL_gpu.h>

#include "shader_bundle.h"

#define SHADER_GPU_UNIFORM_BLOCK_MAX 1024

typedef struct {
    float position[4];
    float tex_coord[2];
} t_shader_gpu_vertex;

typedef struct {
    int bundle_pass_index;
    SDL_GPUGraphicsPipeline *pipeline;
    int vertex_samplers;
    int fragment_samplers;
    int vertex_common_uniform_slot;
    int fragment_common_uniform_slot;
    Uint32 vertex_common_uniform_size;
    Uint32 fragment_common_uniform_size;
    int vertex_param_uniform_slot;
    int fragment_param_uniform_slot;
    Uint32 vertex_param_uniform_size;
    Uint32 fragment_param_uniform_size;
} t_shader_gpu_pass;

typedef struct {
    SDL_GPUDevice *device;
    SDL_GPUShaderFormat shader_format;
    SDL_GPUTextureFormat color_format;
    int num_passes;
    t_shader_gpu_pass *passes;
    char error[512];
} t_shader_gpu_pipeline;

typedef struct {
    float mvp[16];
    float output_size[4];
    float original_size[4];
    float source_size[4];
    Uint32 frame_count;
    Sint32 frame_direction;
} t_shader_gpu_common_uniforms;

typedef struct {
    Uint8 data[SHADER_GPU_UNIFORM_BLOCK_MAX];
    Uint32 size;
} t_shader_gpu_uniform_block;

typedef struct {
    t_shader_gpu_uniform_block vertex_common;
    t_shader_gpu_uniform_block fragment_common;
    t_shader_gpu_uniform_block vertex_param;
    t_shader_gpu_uniform_block fragment_param;
} t_shader_gpu_uniform_blocks;

typedef struct {
    SDL_GPUDevice *device;
    SDL_GPUBuffer *vertex_buffer;
    SDL_GPUBuffer *index_buffer;
    SDL_GPUTexture *source_texture;
    SDL_GPUSampler *source_sampler;
    SDL_GPUTransferBuffer *source_transfer_buffer;
    Uint32 source_transfer_capacity;
    int source_width;
    int source_height;
    char error[512];
} t_shader_gpu_resources;

/*
 * N-pass graph renderer.  Owns the per-pass graphics pipelines, the shared
 * quad/source-upload resources, the intermediate pass-output targets, and the
 * per-pass reflected sampler bindings resolved to RetroArch/Slang texture
 * semantics (Source, Original, PassOutput#, PassPrev#).  The final pass renders
 * directly into the caller target when possible; otherwise it renders to an
 * offscreen target for scale/format conversion or feedback preservation.
 */
typedef enum {
    SHADER_GPU_SEM_SOURCE = 0,
    SHADER_GPU_SEM_ORIGINAL,
    SHADER_GPU_SEM_PASS_OUTPUT,
    SHADER_GPU_SEM_PASS_PREV,
    SHADER_GPU_SEM_LUT,
    SHADER_GPU_SEM_ORIGINAL_HISTORY,
    SHADER_GPU_SEM_PASS_FEEDBACK
} t_shader_gpu_semantic;

typedef struct {
    int slot;                       /* SDL sampler binding slot within stage */
    int fragment;                   /* 1 = fragment stage, 0 = vertex stage */
    t_shader_gpu_semantic semantic;
    int ref;                        /* PASS_OUTPUT: pass index; PASS_PREV: dist;
                                       LUT: lut index */
    SDL_GPUSampler *sampler;        /* not owned; points into the graph cache */
} t_shader_gpu_graph_binding;

typedef struct {
    SDL_GPUTexture *texture;        /* owned; NULL for the direct-final pass */
    int width;
    int height;
    int mip_levels;                /* >1 when a consumer samples with mipmaps */
    int needs_mipmap;              /* output sampled with mipmap_input later */
    int is_feedback;               /* output sampled as PassFeedback next frame */
    SDL_GPUTexture *feedback;      /* owned; previous-frame output (ping-pong) */
    SDL_GPUTextureFormat format;   /* output format (float/sRGB/default) */
    int num_bindings;
    t_shader_gpu_graph_binding *bindings;  /* owned */
} t_shader_gpu_graph_pass;

typedef struct {
    SDL_GPUTexture *texture;        /* owned LUT texture */
    int width;
    int height;
} t_shader_gpu_lut;

#define SHADER_GPU_SAMPLER_CACHE_MAX 16

typedef struct {
    int linear;
    int wrap;
    int mipmap;
    SDL_GPUSampler *sampler;        /* owned */
} t_shader_gpu_sampler_cache_entry;

typedef struct {
    SDL_GPUDevice *device;
    const t_shader_bundle *bundle;  /* not owned; must outlive the graph */
    t_shader_gpu_pipeline pipeline; /* per-pass graphics pipelines */
    t_shader_gpu_resources resources;
    int num_passes;
    t_shader_gpu_graph_pass *passes;
    int num_luts;
    t_shader_gpu_lut *luts;
    t_shader_gpu_sampler_cache_entry sampler_cache[SHADER_GPU_SAMPLER_CACHE_MAX];
    int num_samplers_cached;
    /* When the final pass output does not match the target viewport it renders
       to this offscreen texture and is blitted to the caller target. */
    SDL_GPUTexture *final_offscreen;
    int final_offscreen_w;
    int final_offscreen_h;
    int final_direct;
    /* OriginalHistory ring: history_size = history_depth + 1 source-sized
       slots holding the current frame plus history_depth prior frames. */
    int history_depth;
    int history_size;
    int history_head;
    int history_valid;
    int history_width;
    int history_height;
    SDL_GPUTexture **history;
    SDL_GPUTransferBuffer *history_transfer;
    Uint32 history_transfer_capacity;
    int has_feedback;
    /* 1x1 fallback bound to reflected sampler slots a pass leaves unused so a
       sparse binding range never leaves an undefined descriptor. */
    SDL_GPUTexture *dummy_texture;
    SDL_GPUSampler *dummy_sampler;
    SDL_GPUTextureFormat color_format;
    int source_width;
    int source_height;
    int viewport_width;
    int viewport_height;
    char error[512];
} t_shader_gpu_graph;

void shader_gpu_graph_init ( t_shader_gpu_graph *graph );
void shader_gpu_graph_free ( t_shader_gpu_graph *graph );
int shader_gpu_graph_create ( SDL_GPUDevice *device,
        const t_shader_bundle *bundle, SDL_GPUTextureFormat color_format,
        int source_width, int source_height, t_shader_gpu_graph *graph );
int shader_gpu_graph_set_sizes ( t_shader_gpu_graph *graph, int source_width,
        int source_height, int viewport_width, int viewport_height );
int shader_gpu_graph_render_to_texture ( SDL_GPUCommandBuffer *command_buffer,
        t_shader_gpu_graph *graph, SDL_GPUTexture *final_target,
        const SDL_GPUViewport *final_viewport, const void *pixels, int width,
        int height, int pitch, Uint32 frame_count, Sint32 frame_direction );
/* Rotate history ring and feedback ping-pong roles.  Call only after the
   command buffer that rendered the frame submits successfully. */
void shader_gpu_graph_advance ( t_shader_gpu_graph *graph );

void shader_gpu_pipeline_init ( t_shader_gpu_pipeline *pipeline );
void shader_gpu_pipeline_free ( t_shader_gpu_pipeline *pipeline );
int shader_gpu_pipeline_create ( SDL_GPUDevice *device,
        const t_shader_bundle *bundle, SDL_GPUTextureFormat color_format,
        const SDL_GPUTextureFormat *pass_color_formats,
        t_shader_gpu_pipeline *pipeline );
void shader_gpu_resources_init ( t_shader_gpu_resources *resources );
void shader_gpu_resources_free ( t_shader_gpu_resources *resources );
int shader_gpu_resources_create ( SDL_GPUDevice *device, int source_width,
        int source_height, int linear_filter, t_shader_gpu_resources *resources );
int shader_gpu_resources_upload_source ( SDL_GPUCommandBuffer *command_buffer,
        t_shader_gpu_resources *resources, const void *pixels, int width,
        int height, int pitch );
int shader_gpu_resources_draw_first_pass ( SDL_GPUCommandBuffer *command_buffer,
        SDL_GPURenderPass *render_pass, const t_shader_gpu_pipeline *pipeline,
        const t_shader_gpu_resources *resources,
        const t_shader_gpu_uniform_blocks *uniform_blocks );
void shader_gpu_common_uniforms_fill ( t_shader_gpu_common_uniforms *uniforms,
        int source_width, int source_height, int original_width,
        int original_height, int output_width, int output_height,
        Uint32 frame_count, Sint32 frame_direction );
int shader_gpu_pack_uniform_blocks ( const t_shader_bundle *bundle,
        int pass_index, const t_shader_gpu_common_uniforms *uniforms,
        t_shader_gpu_uniform_blocks *blocks );
SDL_GPUShaderFormat shader_gpu_select_format (
        SDL_GPUShaderFormat available_formats, const t_shader_bundle *bundle );
const char * shader_gpu_format_name ( SDL_GPUShaderFormat format );
const char * shader_gpu_stage_blob_path ( const t_shader_bundle_stage *stage,
        SDL_GPUShaderFormat format );
SDL_GPUTextureFormat shader_gpu_default_color_format ( void );

#endif
