/*
 * Copyright 2019-2021 Joshua Ashton
 * Copyright 2025 Collabora Ltd.
 * SPDX-License-Identifier: Zlib
 *
 * Originally from
 * https://github.com/misyltoad/dxvk-native/tree/master/tests/native
 * and subsequently converted to SDL 3
 */

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

#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <string>
#include <sstream>

#include <d3d11_4.h>
#include <dxgi1_6.h>

#include <wsi/native_wsi.h>

#include "test_utils.h"

using namespace dxvk;

struct Vertex {
  float x, y;
};

struct VsConstants {
  float x, y;
  float w, h;
};

struct VsConstantsPad {
  VsConstants data;
  uint32_t pad[60];
};

struct PsConstants {
  float r, g, b, a;
};

struct DrawOptions {
  bool mapDiscardOnce;
  bool sortByTexture;
  bool drawIndexed;
};

/*
  cbuffer vs_cb : register(b0) {
    float2 v_offset;
    float2 v_scale;
  };
  float4 main(float4 v_pos : IN_POSITION) : SV_POSITION {
    float2 coord = 2.0f * (v_pos * v_scale + v_offset) - 1.0f;
    return float4(coord, 0.0f, 1.0f);
  }
 */

const std::array<uint8_t, 876> g_vertexShaderCode = {{
    0x44, 0x58, 0x42, 0x43, 0x71, 0x79, 0x39, 0xb7, 0xac, 0x61, 0x62, 0xf3, 0x71, 0x08, 0x5c, 0x2c,  // DXBCqy9..ab.q.\,
    0xe6, 0x27, 0x6b, 0xac, 0x01, 0x00, 0x00, 0x00, 0x6c, 0x03, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,  // .'k.....l.......
    0x34, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0xb4, 0x01, 0x00, 0x00, 0xe8, 0x01, 0x00, 0x00,  // 4...............
    0xd0, 0x02, 0x00, 0x00, 0x52, 0x44, 0x45, 0x46, 0x44, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,  // ....RDEFD.......
    0x6c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x01, 0x05, 0xfe, 0xff,  // l.......<.......
    0x00, 0x85, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x13, 0x13, 0x44, 0x25, 0x3c, 0x00, 0x00, 0x00,  // ..........D%<...
    0x18, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,  // ....(...(...$...
    0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ........d.......
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x76, 0x73, 0x5f, 0x63, 0x62, 0x00, 0xab, 0xab, 0x64, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,  // vs_cb...d.......
    0x84, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0xd4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,  // ................
    0xe4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,  // ................
    0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,  // ................
    0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,  // ................
    0x76, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x00, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x00,  // v_offset.float2.
    0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0xdd, 0x00, 0x00, 0x00, 0x76, 0x5f, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x00, 0x4d, 0x69, 0x63, 0x72,  // ....v_scale.Micr
    0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x28, 0x52, 0x29, 0x20, 0x48, 0x4c, 0x53, 0x4c, 0x20, 0x53,  // osoft (R) HLSL S
    0x68, 0x61, 0x64, 0x65, 0x72, 0x20, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x72, 0x20, 0x31,  // hader Compiler 1
    0x30, 0x2e, 0x30, 0x2e, 0x31, 0x30, 0x30, 0x31, 0x31, 0x2e, 0x31, 0x36, 0x33, 0x38, 0x34, 0x00,  // 0.0.10011.16384.
    0x49, 0x53, 0x47, 0x4e, 0x2c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,  // ISGN,...........
    0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,  //  ...............
    0x00, 0x00, 0x00, 0x00, 0x0f, 0x03, 0x00, 0x00, 0x49, 0x4e, 0x5f, 0x50, 0x4f, 0x53, 0x49, 0x54,  // ........IN_POSIT
    0x49, 0x4f, 0x4e, 0x00, 0x4f, 0x53, 0x47, 0x4e, 0x2c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,  // ION.OSGN,.......
    0x08, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,  // .... ...........
    0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x53, 0x56, 0x5f, 0x50,  // ............SV_P
    0x4f, 0x53, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x00, 0x53, 0x48, 0x45, 0x58, 0xe0, 0x00, 0x00, 0x00,  // OSITION.SHEX....
    0x51, 0x00, 0x01, 0x00, 0x38, 0x00, 0x00, 0x00, 0x6a, 0x08, 0x00, 0x01, 0x59, 0x00, 0x00, 0x07,  // Q...8...j...Y...
    0x46, 0x8e, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // F.0.............
    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x03, 0x32, 0x10, 0x10, 0x00,  // ........_...2...
    0x00, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x04, 0xf2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,  // ....g.... ......
    0x01, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x0d,  // ....h.......2...
    0x32, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,  // 2.......F.......
    0xe6, 0x8a, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ..0.............
    0x46, 0x80, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // F.0.............
    0x32, 0x00, 0x00, 0x0f, 0x32, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x10, 0x00,  // 2...2 ......F...
    0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40,  // .....@.....@...@
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x80, 0xbf,  // .........@......
    0x00, 0x00, 0x80, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x08,  // ............6...
    0xc2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // . .......@......
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x3e, 0x00, 0x00, 0x01,  // ...........?>...
    0x53, 0x54, 0x41, 0x54, 0x94, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,  // STAT............
    0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // ............
}};

/*
  Texture2D<float4> tex0 : register(t0);
  cbuffer ps_cb : register(b0) {
    float4 color;
  };
  float4 main() : SV_TARGET {
    return color * tex0.Load(int3(0, 0, 0));
  };
 */

const std::array<uint8_t, 784> g_pixelShaderCode = {{
    0x44, 0x58, 0x42, 0x43, 0x24, 0xb1, 0x22, 0x41, 0x4b, 0x93, 0x1d, 0x4c, 0x42, 0xb2, 0x74, 0x67,  // DXBC$."AK..LB.tg
    0xc4, 0x86, 0x07, 0x32, 0x01, 0x00, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,  // ...2............
    0x34, 0x00, 0x00, 0x00, 0x7c, 0x01, 0x00, 0x00, 0x8c, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,  // 4...|...........
    0x74, 0x02, 0x00, 0x00, 0x52, 0x44, 0x45, 0x46, 0x40, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,  // t...RDEF@.......
    0x98, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x01, 0x05, 0xff, 0xff,  // ........<.......
    0x00, 0x85, 0x00, 0x00, 0x0c, 0x01, 0x00, 0x00, 0x13, 0x13, 0x44, 0x25, 0x3c, 0x00, 0x00, 0x00,  // ..........D%<...
    0x18, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,  // ....(...(...$...
    0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,  // ................
    0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,  // ................
    0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,  // ................
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x78, 0x30, 0x00, 0x70, 0x73, 0x5f,  // ........tex0.ps_
    0x63, 0x62, 0x00, 0xab, 0x91, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00,  // cb..............
    0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00,  // ................
    0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xe8, 0x00, 0x00, 0x00,  // ................
    0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,  // ................
    0x00, 0x00, 0x00, 0x00, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x00, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34,  // ....color.float4
    0x00, 0xab, 0xab, 0xab, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x00, 0x00, 0x00, 0x00, 0xde, 0x00, 0x00, 0x00, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66,  // ........Microsof
    0x74, 0x20, 0x28, 0x52, 0x29, 0x20, 0x48, 0x4c, 0x53, 0x4c, 0x20, 0x53, 0x68, 0x61, 0x64, 0x65,  // t (R) HLSL Shade
    0x72, 0x20, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x72, 0x20, 0x31, 0x30, 0x2e, 0x30, 0x2e,  // r Compiler 10.0.
    0x31, 0x30, 0x30, 0x31, 0x31, 0x2e, 0x31, 0x36, 0x33, 0x38, 0x34, 0x00, 0x49, 0x53, 0x47, 0x4e,  // 10011.16384.ISGN
    0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x4f, 0x53, 0x47, 0x4e,  // ............OSGN
    0x2c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,  // ,........... ...
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x0f, 0x00, 0x00, 0x00, 0x53, 0x56, 0x5f, 0x54, 0x41, 0x52, 0x47, 0x45, 0x54, 0x00, 0xab, 0xab,  // ....SV_TARGET...
    0x53, 0x48, 0x45, 0x58, 0xac, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00,  // SHEX....Q...+...
    0x6a, 0x08, 0x00, 0x01, 0x59, 0x00, 0x00, 0x07, 0x46, 0x8e, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,  // j...Y...F.0.....
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x58, 0x18, 0x00, 0x07, 0x46, 0x7e, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // X...F~0.........
    0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x03,  // ....UU......e...
    0xf2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00,  // . ......h.......
    0x2d, 0x00, 0x00, 0x0b, 0xf2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00,  // -............@..
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x46, 0x7e, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x09,  // F~ .........8...
    0xf2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x0e, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,  // . ......F.......
    0x46, 0x8e, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // F.0.............
    0x3e, 0x00, 0x00, 0x01, 0x53, 0x54, 0x41, 0x54, 0x94, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,  // >...STAT........
    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,  // ................
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // ................
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // ................
}};

class TriangleApp {
  
public:
  
  TriangleApp(SDL_Window* window)
  : m_window(window) {
    Com<ID3D11Device> device;

    D3D_FEATURE_LEVEL fl = D3D_FEATURE_LEVEL_11_1;

    HRESULT status = D3D11CreateDevice(
      nullptr, D3D_DRIVER_TYPE_HARDWARE,
      nullptr, 0, &fl, 1, D3D11_SDK_VERSION,
      &device, nullptr, nullptr);

    if (FAILED(status)) {
      std::cerr << "Failed to create D3D11 device" << std::endl;
      return;
    }
    
    if (FAILED(device->QueryInterface(IID_PPV_ARGS(&m_device)))) {
      std::cerr << "Failed to query ID3D11DeviceContext1" << std::endl;
      return;
    }

    Com<IDXGIDevice> dxgiDevice;

    if (FAILED(m_device->QueryInterface(IID_PPV_ARGS(&dxgiDevice)))) {
      std::cerr << "Failed to query DXGI device" << std::endl;
      return;
    }

    if (FAILED(dxgiDevice->GetAdapter(&m_adapter))) {
      std::cerr << "Failed to query DXGI adapter" << std::endl;
      return;
    }

    if (FAILED(m_adapter->GetParent(IID_PPV_ARGS(&m_factory)))) {
      std::cerr << "Failed to query DXGI factory" << std::endl;
      return;
    }

    m_device->GetImmediateContext1(&m_context);

    DXGI_SWAP_CHAIN_DESC1 swapDesc;
    swapDesc.Width          = m_windowSizeW;
    swapDesc.Height         = m_windowSizeH;
    swapDesc.Format         = DXGI_FORMAT_R8G8B8A8_UNORM;
    swapDesc.Stereo         = FALSE;
    swapDesc.SampleDesc     = { 1, 0 };
    swapDesc.BufferUsage    = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    swapDesc.BufferCount    = 3;
    swapDesc.Scaling        = DXGI_SCALING_STRETCH;
    swapDesc.SwapEffect     = DXGI_SWAP_EFFECT_FLIP_DISCARD;
    swapDesc.AlphaMode      = DXGI_ALPHA_MODE_UNSPECIFIED;
    swapDesc.Flags          = DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;

    DXGI_SWAP_CHAIN_FULLSCREEN_DESC fsDesc;
    fsDesc.RefreshRate      = { 0, 0 };
    fsDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
    fsDesc.Scaling          = DXGI_MODE_SCALING_UNSPECIFIED;
    fsDesc.Windowed         = TRUE;
    
    Com<IDXGISwapChain1> swapChain;
    if (FAILED(m_factory->CreateSwapChainForHwnd(m_device.ptr(), wsi::toHwnd(m_window), &swapDesc, &fsDesc, nullptr, &swapChain))) {
      std::cerr << "Failed to create DXGI swap chain" << std::endl;
      return;
    }
    
    if (FAILED(swapChain->QueryInterface(IID_PPV_ARGS(&m_swapChain)))) {
      std::cerr << "Failed to query DXGI swap chain interface" << std::endl;
      return;
    }

    m_factory->MakeWindowAssociation(m_window, 0);
    
    if (FAILED(m_device->CreateVertexShader(
        g_vertexShaderCode.data(),
        g_vertexShaderCode.size(),
        nullptr, &m_vs))) {
      std::cerr << "Failed to create vertex shader" << std::endl;
      return;
    }
    
    if (FAILED(m_device->CreatePixelShader(
        g_pixelShaderCode.data(),
        g_pixelShaderCode.size(),
        nullptr, &m_ps))) {
      std::cerr << "Failed to create pixel shader" << std::endl;
      return;
    }
    
    std::array<D3D11_INPUT_ELEMENT_DESC, 1> vertexFormatDesc = {{
      { "IN_POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    }};
    
    if (FAILED(m_device->CreateInputLayout(
        vertexFormatDesc.data(),
        vertexFormatDesc.size(),
        g_vertexShaderCode.data(),
        g_vertexShaderCode.size(),
        &m_vertexFormat))) {
      std::cerr << "Failed to create input layout" << std::endl;
      return;
    }

    std::array<Vertex, 6> vertexData = {{
      Vertex { -0.3f, 0.1f },
      Vertex {  0.5f, 0.9f },
      Vertex {  1.3f, 0.1f },
      Vertex { -0.3f, 0.9f },
      Vertex {  1.3f, 0.9f },
      Vertex {  0.5f, 0.1f },
    }};

    D3D11_BUFFER_DESC vboDesc;
    vboDesc.ByteWidth           = sizeof(vertexData);
    vboDesc.Usage               = D3D11_USAGE_IMMUTABLE;
    vboDesc.BindFlags           = D3D11_BIND_VERTEX_BUFFER;
    vboDesc.CPUAccessFlags      = 0;
    vboDesc.MiscFlags           = 0;
    vboDesc.StructureByteStride = 0;

    D3D11_SUBRESOURCE_DATA vboData;
    vboData.pSysMem             = vertexData.data();
    vboData.SysMemPitch         = vboDesc.ByteWidth;
    vboData.SysMemSlicePitch    = vboDesc.ByteWidth;

    if (FAILED(m_device->CreateBuffer(&vboDesc, &vboData, &m_vbo))) {
      std::cerr << "Failed to create index buffer" << std::endl;
      return;
    }

    std::array<uint32_t, 6> indexData = {{ 0, 1, 2, 3, 4, 5 }};

    D3D11_BUFFER_DESC iboDesc;
    iboDesc.ByteWidth           = sizeof(indexData);
    iboDesc.Usage               = D3D11_USAGE_IMMUTABLE;
    iboDesc.BindFlags           = D3D11_BIND_INDEX_BUFFER;
    iboDesc.CPUAccessFlags      = 0;
    iboDesc.MiscFlags           = 0;
    iboDesc.StructureByteStride = 0;

    D3D11_SUBRESOURCE_DATA iboData;
    iboData.pSysMem             = indexData.data();
    iboData.SysMemPitch         = iboDesc.ByteWidth;
    iboData.SysMemSlicePitch    = iboDesc.ByteWidth;

    if (FAILED(m_device->CreateBuffer(&iboDesc, &iboData, &m_ibo))) {
      std::cerr << "Failed to create index buffer" << std::endl;
      return;
    }

    D3D11_BUFFER_DESC cbDesc;
    cbDesc.ByteWidth            = sizeof(PsConstants);
    cbDesc.Usage                = D3D11_USAGE_DYNAMIC;
    cbDesc.BindFlags            = D3D11_BIND_CONSTANT_BUFFER;
    cbDesc.CPUAccessFlags       = D3D11_CPU_ACCESS_WRITE;
    cbDesc.MiscFlags            = 0;
    cbDesc.StructureByteStride  = 0;

    if (FAILED(m_device->CreateBuffer(&cbDesc, nullptr, &m_cbPs))) {
      std::cerr << "Failed to create constant buffer" << std::endl;
      return;
    }

    cbDesc.ByteWidth            = sizeof(VsConstantsPad) * 128 * 8;

    if (FAILED(m_device->CreateBuffer(&cbDesc, nullptr, &m_cbVs))) {
      std::cerr << "Failed to create constant buffer" << std::endl;
      return;
    }

    std::array<uint32_t, 2> colors = { 0xFFFFFFFF, 0xFFC0C0C0 };

    D3D11_SUBRESOURCE_DATA texData;
    texData.pSysMem             = &colors[0];
    texData.SysMemPitch         = sizeof(colors[0]);
    texData.SysMemSlicePitch    = sizeof(colors[0]);

    D3D11_TEXTURE2D_DESC texDesc;
    texDesc.Width               = 1;
    texDesc.Height              = 1;
    texDesc.MipLevels           = 1;
    texDesc.ArraySize           = 1;
    texDesc.Format              = DXGI_FORMAT_R8G8B8A8_UNORM;
    texDesc.SampleDesc          = { 1, 0 };
    texDesc.Usage               = D3D11_USAGE_IMMUTABLE;
    texDesc.BindFlags           = D3D11_BIND_SHADER_RESOURCE;
    texDesc.CPUAccessFlags      = 0;
    texDesc.MiscFlags           = 0;

    if (FAILED(m_device->CreateTexture2D(&texDesc, &texData, &m_tex0))) {
      std::cerr << "Failed to create texture" << std::endl;
      return;
    }

    texData.pSysMem             = &colors[1];

    if (FAILED(m_device->CreateTexture2D(&texDesc, &texData, &m_tex1))) {
      std::cerr << "Failed to create texture" << std::endl;
      return;
    }

    if (FAILED(m_device->CreateShaderResourceView(m_tex0.ptr(), nullptr, &m_srv0))
     || FAILED(m_device->CreateShaderResourceView(m_tex1.ptr(), nullptr, &m_srv1))) {
      std::cerr << "Failed to create SRV" << std::endl;
      return;
    }

    Com<ID3D11Texture2D> backBuffer;
    if (FAILED(m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer)))) {
      std::cerr << "Failed to get swap chain back buffer" << std::endl;
      return;
    }

    D3D11_RENDER_TARGET_VIEW_DESC rtvDesc;
    rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
    rtvDesc.Format        = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
    rtvDesc.Texture2D     = { 0u };
    
    if (FAILED(m_device->CreateRenderTargetView(backBuffer.ptr(), &rtvDesc, &m_rtv))) {
      std::cerr << "Failed to create render target view" << std::endl;
      return;
    }

    m_initialized = true;
  }
  
  
  ~TriangleApp() {
    m_context->ClearState();
  }
  
  
  bool run() {
    if (!m_initialized)
      return false;

    if (m_occluded && (m_occluded = isOccluded()))
      return true;

    if (!beginFrame())
      return true;

    std::array<PsConstants, 2> colors = {{
      PsConstants { 0.25f, 0.25f, 0.25f, 1.0f },
      PsConstants { 0.40f, 0.40f, 0.40f, 1.0f },
    }};

    for (uint32_t i = 0; i < 8; i++) {
      DrawOptions options;
      options.sortByTexture = i & 1;
      options.drawIndexed = i & 2;
      options.mapDiscardOnce = i & 4;
      drawLines(colors[i & 1], options, i);
    }

    if (!endFrame())
      return false;

    return true;
  }


  void drawLines(const PsConstants& psData, const DrawOptions& options, uint32_t baseY) {
    D3D11_MAPPED_SUBRESOURCE sr;

    // Update color for the row
    m_context->PSSetConstantBuffers(0, 1, &m_cbPs);
    m_context->Map(m_cbPs.ptr(), 0, D3D11_MAP_WRITE_DISCARD, 0, &sr);
    std::memcpy(sr.pData, &psData, sizeof(psData));
    m_context->Unmap(m_cbPs.ptr(), 0);

    baseY *= 8;

    if (options.mapDiscardOnce) {
      uint32_t drawIndex = 0;

      // Discard and map the entire vertex constant buffer
      // once, then bind sub-ranges while emitting draw calls
      m_context->Map(m_cbVs.ptr(), 0, D3D11_MAP_WRITE_DISCARD, 0, &sr);
      auto vsData = reinterpret_cast<VsConstantsPad*>(sr.pData);

      for (uint32_t y = 0; y < 8; y++) {
        for (uint32_t x = 0; x < 128; x++)
          vsData[drawIndex++].data = getVsConstants(x, baseY + y);
      }

      m_context->Unmap(m_cbVs.ptr(), 0);
    }

    if (options.drawIndexed)
      m_context->IASetIndexBuffer(m_ibo.ptr(), DXGI_FORMAT_R32_UINT, 0);

    uint32_t vsStride = sizeof(Vertex);
    uint32_t vsOffset = 0;
    m_context->IASetVertexBuffers(0, 1, &m_vbo, &vsStride, &vsOffset);

    uint32_t maxZ = options.sortByTexture ? 2 : 1;

    for (uint32_t z = 0; z < maxZ; z++) {
      uint32_t drawIndex = z;

      if (options.sortByTexture) {
        ID3D11ShaderResourceView* view = z ? m_srv1.ptr() : m_srv0.ptr();
        m_context->PSSetShaderResources(0, 1, &view);
      }

      for (uint32_t y = 0; y < 8; y++) {
        for (uint32_t x = z; x < 128; x += maxZ) {
          uint32_t triIndex = (x ^ y) & 1;

          if (!options.mapDiscardOnce) {
            D3D11_MAP mapMode = drawIndex ? D3D11_MAP_WRITE_NO_OVERWRITE : D3D11_MAP_WRITE_DISCARD;
            m_context->Map(m_cbVs.ptr(), 0, mapMode, 0, &sr);
            auto vsData = reinterpret_cast<VsConstantsPad*>(sr.pData);
            vsData[drawIndex].data = getVsConstants(x, baseY + y);
            m_context->Unmap(m_cbVs.ptr(), 0);
          }

          uint32_t constantOffset = 16 * drawIndex;
          uint32_t constantCount  = 16;
          m_context->VSSetConstantBuffers1(0, 1, &m_cbVs, &constantOffset, &constantCount);

          if (!options.sortByTexture) {
            ID3D11ShaderResourceView* view = triIndex ? m_srv1.ptr() : m_srv0.ptr();
            m_context->PSSetShaderResources(0, 1, &view);
          }

          // Submit draw call
          uint32_t baseIndex = 3 * triIndex;

          if (options.drawIndexed)
            m_context->DrawIndexed(3, baseIndex, 0);
          else
            m_context->Draw(3, baseIndex);

          drawIndex += maxZ;
        }
      }
    }
  }


  static VsConstants getVsConstants(uint32_t x, uint32_t y) {
    VsConstants result;
    result.x = float(x) / 128.0f;
    result.y = float(y) / 64.0f;
    result.w = 1.0f / 128.0f;
    result.h = 1.0f / 64.0f;
    return result;
  }


  bool beginFrame() {
    int32_t w, h;
    SDL_GetWindowSizeInPixels(m_window, &w, &h);

    uint32_t newWindowSizeW = uint32_t(w);
    uint32_t newWindowSizeH = uint32_t(h);
    
    if (m_windowSizeW != newWindowSizeW || m_windowSizeH != newWindowSizeH) {
      m_rtv = nullptr;
      m_context->ClearState();

      DXGI_SWAP_CHAIN_DESC1 desc;
      m_swapChain->GetDesc1(&desc);

      if (FAILED(m_swapChain->ResizeBuffers(desc.BufferCount,
          newWindowSizeW, newWindowSizeH, desc.Format, desc.Flags))) {
        std::cerr << "Failed to resize back buffers" << std::endl;
        return false;
      }
      
      Com<ID3D11Texture2D> backBuffer;
      if (FAILED(m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer)))) {
        std::cerr << "Failed to get swap chain back buffer" << std::endl;
        return false;
      }
      
      D3D11_RENDER_TARGET_VIEW_DESC rtvDesc;
      rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
      rtvDesc.Format        = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
      rtvDesc.Texture2D     = { 0u };
      
      if (FAILED(m_device->CreateRenderTargetView(backBuffer.ptr(), &rtvDesc, &m_rtv))) {
        std::cerr << "Failed to create render target view" << std::endl;
        return false;
      }

      m_windowSizeW = newWindowSizeW;
      m_windowSizeH = newWindowSizeH;
    }

    // Set up render state
    FLOAT color[4] = { 0.5f, 0.5f, 0.5f, 1.0f };
    m_context->OMSetRenderTargets(1, &m_rtv, nullptr);
    m_context->ClearRenderTargetView(m_rtv.ptr(), color);

    m_context->VSSetShader(m_vs.ptr(), nullptr, 0);
    m_context->PSSetShader(m_ps.ptr(), nullptr, 0);

    m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    m_context->IASetInputLayout(m_vertexFormat.ptr());

    D3D11_VIEWPORT viewport;
    viewport.TopLeftX     = 0.0f;
    viewport.TopLeftY     = 0.0f;
    viewport.Width        = float(m_windowSizeW);
    viewport.Height       = float(m_windowSizeH);
    viewport.MinDepth     = 0.0f;
    viewport.MaxDepth     = 1.0f;
    m_context->RSSetViewports(1, &viewport);
    return true;
  }


  bool endFrame() {
    HRESULT hr = m_swapChain->Present(0, DXGI_PRESENT_TEST);

    if (hr == S_OK)
      hr = m_swapChain->Present(0, 0);

    m_occluded = hr == DXGI_STATUS_OCCLUDED;
    return true;
  }

  bool isOccluded() {
    return m_swapChain->Present(0, DXGI_PRESENT_TEST) == DXGI_STATUS_OCCLUDED;
  }

private:
  
  SDL_Window*                   m_window;
  uint32_t                      m_windowSizeW = 1024;
  uint32_t                      m_windowSizeH = 600;
  bool                          m_initialized = false;
  bool                          m_occluded = false;
  
  Com<IDXGIFactory3>            m_factory;
  Com<IDXGIAdapter>             m_adapter;
  Com<ID3D11Device1>            m_device;
  Com<ID3D11DeviceContext1>     m_context;
  Com<IDXGISwapChain2>          m_swapChain;

  Com<ID3D11RenderTargetView>   m_rtv;
  Com<ID3D11Buffer>             m_ibo;
  Com<ID3D11Buffer>             m_vbo;
  Com<ID3D11InputLayout>        m_vertexFormat;

  Com<ID3D11Texture2D>          m_tex0;
  Com<ID3D11Texture2D>          m_tex1;
  Com<ID3D11ShaderResourceView> m_srv0;
  Com<ID3D11ShaderResourceView> m_srv1;
  
  Com<ID3D11Buffer>             m_cbPs;
  Com<ID3D11Buffer>             m_cbVs;

  Com<ID3D11VertexShader>       m_vs;
  Com<ID3D11PixelShader>        m_ps;
  
};

int main(int argc, char** argv) {
  if (::setenv("DXVK_WSI_DRIVER", "SDL3", 1) < 0) {
    std::cerr << "setenv:" << std::strerror(errno) << std::endl;
    return 1;
  }

  if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) {
    std::cerr << "Failed to init SDL" << std::endl;
    return 1;
  }

  SDL_Window* window = SDL_CreateWindow(
    "DXVK Native Triangle! - D3D11",
    1024, 600,
    SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE);
  if (!window) {
    std::cerr << "Failed to create SDL window" << std::endl;
    return 1;
  }

  TriangleApp app(window);

  bool running = true;
  while (running) {
    SDL_Event event;
    while (SDL_PollEvent(&event)) {
      switch (event.type) {
        case SDL_EVENT_QUIT:
          running = false;
          break;
        default:
          break;
      }
    }

    if (!app.run())
      break;
  }

  return 0;
}

