/********************************************************************
* TGX shader flag smoke test for Pico W.
*
* This sketch does not use a display. It draws textured triangles into
* a small RAM framebuffer and reports timing/checksums for bilinear
* shader paths so compile-time shader flags can be compared safely.
********************************************************************/

#include <Arduino.h>
#include <tgx.h>

using namespace tgx;

#ifndef TGX_SHADER_GOURAUD_TEXTURE_FLOAT_INCREMENTAL
#define TGX_SHADER_GOURAUD_TEXTURE_FLOAT_INCREMENTAL 1
#endif

#ifndef TGX_SHADER_GOURAUD_RGB565_FLOAT_INCREMENTAL
#define TGX_SHADER_GOURAUD_RGB565_FLOAT_INCREMENTAL 1
#endif

#ifndef TGX_SHADER_RGB565_FAST_TEXTURE_MODULATE
#define TGX_SHADER_RGB565_FAST_TEXTURE_MODULATE 1
#endif

#ifndef TGX_SHADER_RGB565_FAST_BILINEAR
#define TGX_SHADER_RGB565_FAST_BILINEAR 1
#endif

static constexpr int W = 96;
static constexpr int H = 96;
static constexpr int TEX_W = 32;
static constexpr int TEX_H = 32;
static constexpr int PRIMITIVES_PER_FRAME = 4;
static constexpr int WARMUP_FRAMES = 2;
static constexpr int MEASURE_FRAMES = 10;

static uint16_t framebuffer_storage[W * H];
static uint16_t zbuffer_storage[W * H];
static uint16_t texture_storage[TEX_W * TEX_H];

static Image<RGB565> framebuffer(framebuffer_storage, W, H);
static Image<RGB565> texture(texture_storage, TEX_W, TEX_H);

static constexpr Shader LOADED_SHADERS =
    SHADER_PERSPECTIVE |
    SHADER_ZBUFFER |
    SHADER_UNLIT |
    SHADER_FLAT |
    SHADER_GOURAUD |
    SHADER_TEXTURE_BILINEAR |
    SHADER_TEXTURE_NEAREST |
    SHADER_TEXTURE_WRAP_POW2;

static Renderer3D<RGB565, LOADED_SHADERS, uint16_t> renderer({ W, H }, &framebuffer, zbuffer_storage);

static RGB565 rgb888(int r, int g, int b)
    {
    return RGB565(r / 255.0f, g / 255.0f, b / 255.0f);
    }

static void initializeTexture()
    {
    for (int y = 0; y < TEX_H; y++)
        {
        for (int x = 0; x < TEX_W; x++)
            {
            const int checker = ((x >> 2) ^ (y >> 2)) & 1;
            const int r = checker ? 220 : (20 + x * 5);
            const int g = checker ? (30 + y * 6) : 180;
            const int b = checker ? 40 : (30 + ((x + y) * 3));
            texture_storage[y * TEX_W + x] = rgb888(r & 255, g & 255, b & 255).val;
            }
        }
    }

static uint32_t checksumFrame()
    {
    uint32_t h = 2166136261u;
    for (int i = 0; i < W * H; i++)
        {
        h ^= framebuffer_storage[i];
        h *= 16777619u;
        }
    for (int i = 0; i < W * H; i += 17)
        {
        h ^= zbuffer_storage[i];
        h *= 16777619u;
        }
    return h;
    }

static void drawPrimitiveSet()
    {
    for (int i = 0; i < PRIMITIVES_PER_FRAME; i++)
        {
        fMat4 M;
        M.setRotate(17.0f + 21.0f * i, { 0.0f, 1.0f, 0.0f });
        M.multRotate(11.0f + 13.0f * i, { 1.0f, 0.0f, 0.0f });
        M.multScale({ 0.62f, 0.62f, 0.62f });
        M.multTranslate({ -0.78f + 0.52f * i, -0.12f + 0.07f * i, -4.2f - 0.28f * i });
        renderer.setModelMatrix(M);
        renderer.drawCube(&texture, &texture, &texture, &texture, &texture, &texture);
        }
    }

static uint32_t runCase(const char* name, Shader shader)
    {
    renderer.setShaders(shader | SHADER_TEXTURE_BILINEAR | SHADER_TEXTURE_WRAP_POW2);
    renderer.setTextureQuality(SHADER_TEXTURE_BILINEAR);
    renderer.setTextureWrappingMode(SHADER_TEXTURE_WRAP_POW2);

    uint32_t checksum = 2166136261u;
    for (int i = 0; i < WARMUP_FRAMES; i++)
        {
        framebuffer.fillScreen(RGB565_Black);
        renderer.clearZbuffer();
        drawPrimitiveSet();
        checksum = (checksum ^ checksumFrame() ^ (uint32_t)i) * 16777619u;
        }

    const uint32_t start = micros();
    for (int i = 0; i < MEASURE_FRAMES; i++)
        {
        framebuffer.fillScreen(RGB565_Black);
        renderer.clearZbuffer();
        drawPrimitiveSet();
        checksum = (checksum ^ checksumFrame() ^ (uint32_t)(i + 31)) * 16777619u;
        }
    const uint32_t elapsed = micros() - start;

    Serial.print("CASE=");
    Serial.print(name);
    Serial.print(" FRAMES=");
    Serial.print(MEASURE_FRAMES);
    Serial.print(" PRIMITIVES_PER_FRAME=");
    Serial.print(PRIMITIVES_PER_FRAME);
    Serial.print(" TIME_US=");
    Serial.print(elapsed);
    Serial.print(" AVG_FRAME_US=");
    Serial.print((float)elapsed / MEASURE_FRAMES, 2);
    Serial.print(" AVG_PRIMITIVE_US=");
    Serial.print((float)elapsed / (MEASURE_FRAMES * PRIMITIVES_PER_FRAME), 2);
    Serial.print(" CHECKSUM=");
    Serial.println(checksum, HEX);
    return checksum;
    }

void setup()
    {
    Serial.begin(115200);
    const uint32_t serial_start = millis();
    while ((!Serial) && ((millis() - serial_start) < 8000))
        {
        delay(10);
        }
    delay(200);

    initializeTexture();

    Serial.println("TGX_FAST_BILINEAR_PROBE_BEGIN");
    Serial.print("BOARD=");
#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
    Serial.println("PicoW");
#else
    Serial.println("unknown");
#endif
    Serial.print("TGX_SHADER_GOURAUD_TEXTURE_FLOAT_INCREMENTAL=");
    Serial.println(TGX_SHADER_GOURAUD_TEXTURE_FLOAT_INCREMENTAL);
    Serial.print("TGX_SHADER_GOURAUD_RGB565_FLOAT_INCREMENTAL=");
    Serial.println(TGX_SHADER_GOURAUD_RGB565_FLOAT_INCREMENTAL);
    Serial.print("TGX_SHADER_RGB565_FAST_TEXTURE_MODULATE=");
    Serial.println(TGX_SHADER_RGB565_FAST_TEXTURE_MODULATE);
    Serial.print("TGX_SHADER_RGB565_FAST_BILINEAR=");
    Serial.println(TGX_SHADER_RGB565_FAST_BILINEAR);
    Serial.flush();

    renderer.setViewportSize(W, H);
    renderer.setOffset(0, 0);
    renderer.setImage(&framebuffer);
    renderer.setZbuffer(zbuffer_storage);
    renderer.setCulling(0);
    renderer.setPerspective(45.0f, (float)W / H, 1.0f, 100.0f);
    renderer.setLookAt({ 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, -1.0f }, { 0.0f, 1.0f, 0.0f });
    renderer.setMaterial(RGBf(0.95f, 0.82f, 0.70f), 0.20f, 0.70f, 0.05f, 4);
    renderer.setLight({ -0.45f, -0.35f, -1.0f }, RGBf(0.15f, 0.15f, 0.15f), RGBf(0.85f, 0.85f, 0.85f), RGBf(0.0f, 0.0f, 0.0f));

    const uint32_t flat_hash = runCase("bilinear_flat", SHADER_FLAT);
    const uint32_t gouraud_hash = runCase("bilinear_gouraud", SHADER_GOURAUD);
    const uint32_t unlit_hash = runCase("bilinear_unlit", SHADER_UNLIT);

    Serial.print("SUMMARY_HASH=");
    Serial.println(flat_hash ^ (gouraud_hash * 16777619u) ^ (unlit_hash * 2166136261u), HEX);
    Serial.println("TGX_FAST_BILINEAR_PROBE_END");
    }

void loop()
    {
    delay(1000);
    }
