// Independent teaching source, not verified QubicEngine repository code.
#include <windows.h>
#include <shellapi.h>
#include <wincodec.h>
#include <wrl/client.h>
#include <d3d12.h>
#include <dxgi1_6.h>
#include <DirectXMath.h>
#include <algorithm>
#include <array>
#include <chrono>
#include <cfloat>
#include <cstdio>
#include <cstdint>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <limits>
#include <stdexcept>
#include <string>
#include <vector>
using Microsoft::WRL::ComPtr;
using namespace DirectX;
namespace {
void Check(HRESULT hr) {
    if (FAILED(hr)) {
        char text[64];
        std::snprintf(text, sizeof(text), "DirectX/Windows failure: 0x%08lX", static_cast<unsigned long>(hr));
        throw std::runtime_error(text);
    }
}
D3D12_HEAP_PROPERTIES Heap(D3D12_HEAP_TYPE type) {
    D3D12_HEAP_PROPERTIES h{};
    h.Type = type; h.CreationNodeMask = 1; h.VisibleNodeMask = 1;
    return h;
}
D3D12_RESOURCE_DESC BufferDesc(UINT64 size) {
    D3D12_RESOURCE_DESC d{};
    d.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; d.Width = size;
    d.Height = 1; d.DepthOrArraySize = 1; d.MipLevels = 1;
    d.SampleDesc.Count = 1; d.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
    return d;
}
void Transition(ID3D12GraphicsCommandList* list, ID3D12Resource* resource,
                D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after) {
    D3D12_RESOURCE_BARRIER b{};
    b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
    b.Transition.pResource = resource;
    b.Transition.StateBefore = before; b.Transition.StateAfter = after;
    b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
    list->ResourceBarrier(1, &b);
}
std::vector<char> ReadBytes(const std::filesystem::path& file) {
    std::ifstream stream(file, std::ios::binary | std::ios::ate);
    if (!stream) throw std::runtime_error("Shader file missing. Build shaders and keep the shaders folder next to the executable.");
    const auto size = stream.tellg();
    if (size <= 0) throw std::runtime_error("Empty shader file");
    std::vector<char> bytes(static_cast<size_t>(size));
    stream.seekg(0);
    if (!stream.read(bytes.data(), static_cast<std::streamsize>(bytes.size())))
        throw std::runtime_error("Could not read shader file");
    return bytes;
}
struct Image { UINT width = 0, height = 0; std::vector<uint8_t> pixels; };
Image DecodeImage(const std::filesystem::path& file) {
    Image image;
    if (file.empty()) {
        image.width = image.height = 128;
        image.pixels.resize(128 * 128 * 4);
        for (UINT y = 0; y < 128; ++y) for (UINT x = 0; x < 128; ++x) {
            const bool bright = ((x / 16) + (y / 16)) % 2 == 0;
            const size_t offset = (y * 128 + x) * 4;
            image.pixels[offset] = bright ? 160 : 20;
            image.pixels[offset + 1] = bright ? 235 : 60;
            image.pixels[offset + 2] = bright ? 250 : 80;
            image.pixels[offset + 3] = 255;
        }
        return image;
    }
    ComPtr<IWICImagingFactory> factory;
    Check(CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&factory)));
    ComPtr<IWICBitmapDecoder> decoder;
    Check(factory->CreateDecoderFromFilename(file.c_str(), nullptr, GENERIC_READ,
        WICDecodeMetadataCacheOnLoad, &decoder));
    ComPtr<IWICBitmapFrameDecode> frame;
    Check(decoder->GetFrame(0, &frame));
    Check(frame->GetSize(&image.width, &image.height));
    if (!image.width || !image.height || image.width > 16384 || image.height > 16384)
        throw std::runtime_error("Image dimensions outside the sample's supported limits");
    const UINT64 byteCount = UINT64(image.width) * image.height * 4;
    if (byteCount > std::numeric_limits<UINT>::max()) throw std::runtime_error("Image too large for WIC CopyPixels");
    ComPtr<IWICFormatConverter> converter;
    Check(factory->CreateFormatConverter(&converter));
    Check(converter->Initialize(frame.Get(), GUID_WICPixelFormat32bppRGBA,
        WICBitmapDitherTypeNone, nullptr, 0, WICBitmapPaletteTypeCustom));
    image.pixels.resize(static_cast<size_t>(byteCount));
    Check(converter->CopyPixels(nullptr, image.width * 4, static_cast<UINT>(byteCount), image.pixels.data()));
    return image;
}
struct Vertex { XMFLOAT3 position; XMFLOAT3 color; XMFLOAT2 uv; };
struct FrameContext { ComPtr<ID3D12CommandAllocator> allocator; UINT64 completion = 0; };
class Sample {
public:
    HWND window = nullptr;
    UINT width = 1100, height = 720, pendingWidth = 1100, pendingHeight = 720;
    bool minimized = false;
    std::filesystem::path imageFile;
    bool warp = false;
    static constexpr UINT FrameCount = 2;
    ComPtr<IDXGIFactory6> factory;
    ComPtr<ID3D12Device> device;
    ComPtr<ID3D12CommandQueue> queue;
    ComPtr<IDXGISwapChain3> swap;
    ComPtr<ID3D12DescriptorHeap> rtvs, srvs, dsvs;
    std::array<ComPtr<ID3D12Resource>, FrameCount> backBuffers;
    std::array<FrameContext, FrameCount> frames;
    ComPtr<ID3D12GraphicsCommandList> list;
    ComPtr<ID3D12Fence> fence;
    ComPtr<ID3D12RootSignature> root;
    ComPtr<ID3D12PipelineState> pipeline;
    ComPtr<ID3D12Resource> vertexBuffer, indexBuffer, texture, depth;
    D3D12_VERTEX_BUFFER_VIEW vertexView{};
    D3D12_INDEX_BUFFER_VIEW indexView{};
    UINT indexCount = 0, rtvStride = 0;
    UINT64 nextFence = 1;
    HANDLE event = nullptr;
    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
    ~Sample() {
        if (window && IsWindow(window)) {
            SetWindowLongPtrW(window, GWLP_USERDATA, 0);
            DestroyWindow(window);
        }
        if (event) {
            try { if (queue && fence) Flush(); } catch (...) {}
            CloseHandle(event);
        }
    }
    void Wait(UINT64 value) {
        if (value && fence->GetCompletedValue() < value) {
            Check(fence->SetEventOnCompletion(value, event));
            if (WaitForSingleObject(event, INFINITE) != WAIT_OBJECT_0)
                throw std::runtime_error("Fence event wait failed");
        }
    }
    UINT64 Signal() { UINT64 v = nextFence++; Check(queue->Signal(fence.Get(), v)); return v; }
    void Flush() { Wait(Signal()); }
    D3D12_CPU_DESCRIPTOR_HANDLE Rtv(UINT i) {
        auto h = rtvs->GetCPUDescriptorHandleForHeapStart(); h.ptr += SIZE_T(i) * rtvStride; return h;
    }
    void Init() {
        UINT flags = 0;
#ifdef _DEBUG
        ComPtr<ID3D12Debug> debug;
        if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debug)))) {
            debug->EnableDebugLayer(); flags |= DXGI_CREATE_FACTORY_DEBUG;
        }
#endif
        Check(CreateDXGIFactory2(flags, IID_PPV_ARGS(&factory)));
        ComPtr<IDXGIAdapter1> adapter;
        if (warp) {
            Check(factory->EnumWarpAdapter(IID_PPV_ARGS(&adapter)));
            Check(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&device)));
        } else {
            for (UINT i = 0; ; ++i) {
                const HRESULT result = factory->EnumAdapterByGpuPreference(i,
                    DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, IID_PPV_ARGS(&adapter));
                if (result == DXGI_ERROR_NOT_FOUND) break;
                Check(result);
                DXGI_ADAPTER_DESC1 d{}; Check(adapter->GetDesc1(&d));
                if (!(d.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) && SUCCEEDED(D3D12CreateDevice(
                    adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&device)))) break;
                adapter.Reset();
            }
            if (!device) throw std::runtime_error("No DX12 device. Try --warp for the software teaching path.");
        }
        D3D12_FEATURE_DATA_SHADER_MODEL shaderModel{D3D_SHADER_MODEL_6_0};
        Check(device->CheckFeatureSupport(D3D12_FEATURE_SHADER_MODEL, &shaderModel, sizeof(shaderModel)));
        if (shaderModel.HighestShaderModel < D3D_SHADER_MODEL_6_0)
            throw std::runtime_error("This sample requires Shader Model 6.0.");
        D3D12_COMMAND_QUEUE_DESC q{}; q.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
        Check(device->CreateCommandQueue(&q, IID_PPV_ARGS(&queue)));
        DXGI_SWAP_CHAIN_DESC1 s{}; s.Width = width; s.Height = height; s.BufferCount = FrameCount;
        s.Format = DXGI_FORMAT_R8G8B8A8_UNORM; s.SampleDesc.Count = 1;
        s.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; s.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
        ComPtr<IDXGISwapChain1> base;
        Check(factory->CreateSwapChainForHwnd(queue.Get(), window, &s, nullptr, nullptr, &base));
        Check(base.As(&swap)); Check(factory->MakeWindowAssociation(window, DXGI_MWA_NO_ALT_ENTER));
        D3D12_DESCRIPTOR_HEAP_DESC h{}; h.NumDescriptors = FrameCount; h.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
        Check(device->CreateDescriptorHeap(&h, IID_PPV_ARGS(&rtvs)));
        rtvStride = device->GetDescriptorHandleIncrementSize(h.Type);
        h.NumDescriptors = 1; h.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; h.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
        Check(device->CreateDescriptorHeap(&h, IID_PPV_ARGS(&srvs)));
        h.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV; h.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
        Check(device->CreateDescriptorHeap(&h, IID_PPV_ARGS(&dsvs)));
        for (auto& f : frames) Check(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&f.allocator)));
        Check(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, frames[0].allocator.Get(), nullptr, IID_PPV_ARGS(&list)));
        Check(list->Close());
        Check(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)));
        event = CreateEventW(nullptr, FALSE, FALSE, nullptr);
        if (!event) throw std::runtime_error("CreateEvent failed");
        CreateTargets(); CreatePipeline(); CreateGeometry();
#if TEXTURED
        CreateTexture();
#endif
    }
    void CreateTargets() {
        D3D12_RENDER_TARGET_VIEW_DESC r{}; r.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
        r.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
        for (UINT i = 0; i < FrameCount; ++i) {
            Check(swap->GetBuffer(i, IID_PPV_ARGS(&backBuffers[i])));
            device->CreateRenderTargetView(backBuffers[i].Get(), &r, Rtv(i));
        }
#if TEXTURED
        D3D12_RESOURCE_DESC d{}; d.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
        d.Width = width; d.Height = height; d.DepthOrArraySize = 1; d.MipLevels = 1;
        d.Format = DXGI_FORMAT_D32_FLOAT; d.SampleDesc.Count = 1; d.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
        D3D12_CLEAR_VALUE clear{}; clear.Format = DXGI_FORMAT_D32_FLOAT; clear.DepthStencil.Depth = 1.0f;
        auto h = Heap(D3D12_HEAP_TYPE_DEFAULT);
        Check(device->CreateCommittedResource(&h, D3D12_HEAP_FLAG_NONE, &d, D3D12_RESOURCE_STATE_DEPTH_WRITE,
            &clear, IID_PPV_ARGS(&depth)));
        device->CreateDepthStencilView(depth.Get(), nullptr, dsvs->GetCPUDescriptorHandleForHeapStart());
#endif
    }
    void CreatePipeline() {
        D3D12_DESCRIPTOR_RANGE range{}; range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
        range.NumDescriptors = 1; range.BaseShaderRegister = 0; range.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
        D3D12_ROOT_PARAMETER params[2]{};
        params[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS;
        params[0].Constants.Num32BitValues = 16; params[0].Constants.ShaderRegister = 0;
        params[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX;
        params[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
        params[1].DescriptorTable.NumDescriptorRanges = 1; params[1].DescriptorTable.pDescriptorRanges = &range;
        params[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
        D3D12_STATIC_SAMPLER_DESC sampler{}; sampler.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
        sampler.AddressU = sampler.AddressV = sampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
        sampler.ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS; sampler.MaxLOD = FLT_MAX;
        sampler.MaxAnisotropy = 1; sampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
        D3D12_ROOT_SIGNATURE_DESC d{}; d.NumParameters = TEXTURED ? 2 : 1; d.pParameters = params;
        d.NumStaticSamplers = TEXTURED ? 1 : 0; d.pStaticSamplers = &sampler;
        d.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
        ComPtr<ID3DBlob> blob, error;
        const HRESULT serialized = D3D12SerializeRootSignature(&d, D3D_ROOT_SIGNATURE_VERSION_1, &blob, &error);
        if (error) OutputDebugStringA(static_cast<const char*>(error->GetBufferPointer()));
        Check(serialized);
        Check(device->CreateRootSignature(0, blob->GetBufferPointer(), blob->GetBufferSize(), IID_PPV_ARGS(&root)));
        wchar_t executable[MAX_PATH];
        const DWORD pathLength = GetModuleFileNameW(nullptr, executable, MAX_PATH);
        if (!pathLength || pathLength == MAX_PATH) throw std::runtime_error("Executable path unavailable or exceeds this sample's MAX_PATH limit");
        const auto dir = std::filesystem::path(executable).parent_path() / L"shaders" / (TEXTURED ? L"texture" : L"triangle");
        const auto vs = ReadBytes(dir / L"vs.cso"), ps = ReadBytes(dir / L"ps.cso");
        const D3D12_INPUT_ELEMENT_DESC layout[] = {
            {"POSITION",0,DXGI_FORMAT_R32G32B32_FLOAT,0,0,D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,0},
            {"COLOR",0,DXGI_FORMAT_R32G32B32_FLOAT,0,12,D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,0},
            {"TEXCOORD",0,DXGI_FORMAT_R32G32_FLOAT,0,24,D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,0}
        };
        D3D12_GRAPHICS_PIPELINE_STATE_DESC p{};
        p.pRootSignature = root.Get(); p.VS = {vs.data(), vs.size()}; p.PS = {ps.data(), ps.size()};
        p.InputLayout = {layout, 3}; p.SampleMask = UINT_MAX;
        auto& blend = p.BlendState.RenderTarget[0];
        blend.SrcBlend = D3D12_BLEND_ONE; blend.DestBlend = D3D12_BLEND_ZERO;
        blend.BlendOp = D3D12_BLEND_OP_ADD; blend.SrcBlendAlpha = D3D12_BLEND_ONE;
        blend.DestBlendAlpha = D3D12_BLEND_ZERO; blend.BlendOpAlpha = D3D12_BLEND_OP_ADD;
        blend.LogicOp = D3D12_LOGIC_OP_NOOP; blend.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
        p.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
        p.RasterizerState.CullMode = D3D12_CULL_MODE_NONE; // Teaching geometry, both sides visible.
        p.RasterizerState.DepthClipEnable = TRUE;
        p.DepthStencilState.DepthEnable = TEXTURED;
        p.DepthStencilState.DepthWriteMask = TEXTURED ? D3D12_DEPTH_WRITE_MASK_ALL : D3D12_DEPTH_WRITE_MASK_ZERO;
        p.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_LESS;
        p.DepthStencilState.StencilReadMask = p.DepthStencilState.StencilWriteMask = D3D12_DEFAULT_STENCIL_READ_MASK;
        p.DepthStencilState.FrontFace = {D3D12_STENCIL_OP_KEEP,D3D12_STENCIL_OP_KEEP,D3D12_STENCIL_OP_KEEP,D3D12_COMPARISON_FUNC_ALWAYS};
        p.DepthStencilState.BackFace = p.DepthStencilState.FrontFace;
        p.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
        p.NumRenderTargets = 1; p.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
        p.DSVFormat = TEXTURED ? DXGI_FORMAT_D32_FLOAT : DXGI_FORMAT_UNKNOWN; p.SampleDesc.Count = 1;
        Check(device->CreateGraphicsPipelineState(&p, IID_PPV_ARGS(&pipeline)));
    }
    ComPtr<ID3D12Resource> UploadBuffer(const void* data, size_t size) {
        auto h = Heap(D3D12_HEAP_TYPE_UPLOAD); auto d = BufferDesc(size);
        ComPtr<ID3D12Resource> buffer;
        Check(device->CreateCommittedResource(&h, D3D12_HEAP_FLAG_NONE, &d,
            D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&buffer)));
        void* mapped = nullptr; D3D12_RANGE noRead{0,0};
        Check(buffer->Map(0, &noRead, &mapped)); std::memcpy(mapped, data, size);
        D3D12_RANGE written{0,size}; buffer->Unmap(0,&written); return buffer;
    }
    void CreateGeometry() {
        std::vector<Vertex> vertices; std::vector<uint16_t> indices;
#if TEXTURED
        const XMFLOAT3 corners[] = {{-1,-1,-1},{1,-1,-1},{1,1,-1},{-1,1,-1},
                                   {-1,-1,1},{1,-1,1},{1,1,1},{-1,1,1}};
        const int faces[6][4] = {{0,1,2,3},{5,4,7,6},{4,0,3,7},{1,5,6,2},{3,2,6,7},{4,5,1,0}};
        const XMFLOAT2 uvs[] = {{0,1},{1,1},{1,0},{0,0}};
        for (int f = 0; f < 6; ++f) {
            const uint16_t base = static_cast<uint16_t>(vertices.size());
            const float brightness = 0.7f + 0.05f * f; // Face tint, not a lighting system.
            for (int i = 0; i < 4; ++i) vertices.push_back({corners[faces[f][i]],{brightness,brightness,brightness},uvs[i]});
            for (uint16_t i : {0,1,2,0,2,3}) indices.push_back(base+i);
        }
#else
        vertices = {{{0,0.7f,0},{0.6f,0.95f,1},{0,0}},
                    {{0.7f,-0.7f,0},{0.55f,0.6f,1},{1,1}},
                    {{-0.7f,-0.7f,0},{0.3f,0.9f,0.6f},{0,1}}};
        indices = {0,1,2};
#endif
        const UINT vbSize = static_cast<UINT>(vertices.size()*sizeof(Vertex));
        const UINT ibSize = static_cast<UINT>(indices.size()*sizeof(uint16_t));
        vertexBuffer = UploadBuffer(vertices.data(), vbSize); indexBuffer = UploadBuffer(indices.data(),ibSize);
        vertexView = {vertexBuffer->GetGPUVirtualAddress(),vbSize,sizeof(Vertex)};
        indexView = {indexBuffer->GetGPUVirtualAddress(),ibSize,DXGI_FORMAT_R16_UINT};
        indexCount = static_cast<UINT>(indices.size());
    }
    void CreateTexture() {
        const Image image = DecodeImage(imageFile);
        D3D12_RESOURCE_DESC d{}; d.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
        d.Width = image.width; d.Height = image.height; d.DepthOrArraySize = 1; d.MipLevels = 1;
        d.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; d.SampleDesc.Count = 1;
        auto gpuHeap = Heap(D3D12_HEAP_TYPE_DEFAULT);
        Check(device->CreateCommittedResource(&gpuHeap,D3D12_HEAP_FLAG_NONE,&d,
            D3D12_RESOURCE_STATE_COPY_DEST,nullptr,IID_PPV_ARGS(&texture)));
        D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint{}; UINT rows = 0;
        UINT64 rowBytes = 0, uploadSize = 0;
        device->GetCopyableFootprints(&d,0,1,0,&footprint,&rows,&rowBytes,&uploadSize);
        auto uploadHeap = Heap(D3D12_HEAP_TYPE_UPLOAD); auto uploadDesc = BufferDesc(uploadSize);
        ComPtr<ID3D12Resource> staging;
        Check(device->CreateCommittedResource(&uploadHeap,D3D12_HEAP_FLAG_NONE,&uploadDesc,
            D3D12_RESOURCE_STATE_GENERIC_READ,nullptr,IID_PPV_ARGS(&staging)));
        uint8_t* mapped = nullptr; D3D12_RANGE noRead{0,0};
        Check(staging->Map(0,&noRead,reinterpret_cast<void**>(&mapped)));
        for (UINT y = 0; y < rows; ++y)
            std::memcpy(mapped+footprint.Offset+size_t(y)*footprint.Footprint.RowPitch,
                image.pixels.data()+size_t(y)*image.width*4,static_cast<size_t>(rowBytes));
        D3D12_RANGE written{0,static_cast<SIZE_T>(uploadSize)}; staging->Unmap(0,&written);
        Check(frames[0].allocator->Reset()); Check(list->Reset(frames[0].allocator.Get(),nullptr));
        D3D12_TEXTURE_COPY_LOCATION src{}; src.pResource = staging.Get();
        src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; src.PlacedFootprint = footprint;
        D3D12_TEXTURE_COPY_LOCATION dst{}; dst.pResource = texture.Get(); dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
        list->CopyTextureRegion(&dst,0,0,0,&src,nullptr);
        Transition(list.Get(),texture.Get(),D3D12_RESOURCE_STATE_COPY_DEST,D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
        Check(list->Close()); ID3D12CommandList* commands[] = {list.Get()}; queue->ExecuteCommandLists(1,commands);
        Flush(); // Initialization only: staging stays alive through the completed copy.
        D3D12_SHADER_RESOURCE_VIEW_DESC srv{}; srv.Format = d.Format;
        srv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; srv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srv.Texture2D.MipLevels = 1;
        device->CreateShaderResourceView(texture.Get(),&srv,srvs->GetCPUDescriptorHandleForHeapStart());
    }
    void ResizeIfNeeded() {
        if (!pendingWidth || !pendingHeight || (width == pendingWidth && height == pendingHeight)) return;
        Flush(); for (auto& b : backBuffers) b.Reset(); depth.Reset();
        width = pendingWidth; height = pendingHeight;
        Check(swap->ResizeBuffers(FrameCount,width,height,DXGI_FORMAT_R8G8B8A8_UNORM,0));
        for (auto& f : frames) f.completion = 0;
        CreateTargets();
    }
    void Render() {
        ResizeIfNeeded();
        const UINT index = swap->GetCurrentBackBufferIndex(); auto& frame = frames[index];
        Wait(frame.completion); Check(frame.allocator->Reset()); Check(list->Reset(frame.allocator.Get(),pipeline.Get()));
        Transition(list.Get(),backBuffers[index].Get(),D3D12_RESOURCE_STATE_PRESENT,D3D12_RESOURCE_STATE_RENDER_TARGET);
        const auto rtv = Rtv(index); const auto dsv = dsvs->GetCPUDescriptorHandleForHeapStart();
        const float clear[] = {0.01f,0.025f,0.045f,1}; list->ClearRenderTargetView(rtv,clear,0,nullptr);
        list->OMSetRenderTargets(1,&rtv,FALSE,TEXTURED?&dsv:nullptr);
#if TEXTURED
        list->ClearDepthStencilView(dsv,D3D12_CLEAR_FLAG_DEPTH,1.0f,0,0,nullptr);
#endif
        const D3D12_VIEWPORT viewport{0,0,float(width),float(height),0,1};
        const D3D12_RECT scissor{0,0,LONG(width),LONG(height)};
        list->RSSetViewports(1,&viewport); list->RSSetScissorRects(1,&scissor);
        list->SetGraphicsRootSignature(root.Get());
        XMMATRIX matrix = XMMatrixIdentity();
#if TEXTURED
        const float t = std::chrono::duration<float>(std::chrono::steady_clock::now()-start).count();
        matrix = XMMatrixRotationY(t*0.45f) * XMMatrixRotationX(0.3f)
            * XMMatrixLookAtLH(XMVectorSet(0,1,-5,1),XMVectorZero(),XMVectorSet(0,1,0,0))
            * XMMatrixPerspectiveFovLH(XM_PIDIV4,float(width)/height,0.1f,100.0f);
        ID3D12DescriptorHeap* heaps[] = {srvs.Get()}; list->SetDescriptorHeaps(1,heaps);
        list->SetGraphicsRootDescriptorTable(1,srvs->GetGPUDescriptorHandleForHeapStart());
#endif
        XMFLOAT4X4 stored; XMStoreFloat4x4(&stored,matrix);
        list->SetGraphicsRoot32BitConstants(0,16,&stored,0);
        list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
        list->IASetVertexBuffers(0,1,&vertexView); list->IASetIndexBuffer(&indexView);
        list->DrawIndexedInstanced(indexCount,1,0,0,0);
        Transition(list.Get(),backBuffers[index].Get(),D3D12_RESOURCE_STATE_RENDER_TARGET,D3D12_RESOURCE_STATE_PRESENT);
        Check(list->Close()); ID3D12CommandList* commands[] = {list.Get()}; queue->ExecuteCommandLists(1,commands);
        Check(swap->Present(1,0)); frame.completion = Signal();
    }
};
LRESULT CALLBACK WindowProc(HWND window, UINT message, WPARAM w, LPARAM l) {
    auto* app = reinterpret_cast<Sample*>(GetWindowLongPtrW(window,GWLP_USERDATA));
    if (message == WM_NCCREATE) {
        app = static_cast<Sample*>(reinterpret_cast<CREATESTRUCTW*>(l)->lpCreateParams);
        SetWindowLongPtrW(window,GWLP_USERDATA,reinterpret_cast<LONG_PTR>(app));
    }
    if (message == WM_SIZE && app) {
        app->minimized = w == SIZE_MINIMIZED;
        app->pendingWidth = LOWORD(l); app->pendingHeight = HIWORD(l); return 0;
    }
    if (message == WM_KEYDOWN && w == VK_ESCAPE) { DestroyWindow(window); return 0; }
    if (message == WM_DESTROY) { PostQuitMessage(0); return 0; }
    if (message == WM_PAINT) { PAINTSTRUCT ps; BeginPaint(window,&ps); EndPaint(window,&ps); return 0; }
    return DefWindowProcW(window,message,w,l);
}
}
int WINAPI wWinMain(HINSTANCE instance,HINSTANCE,LPWSTR,int show) {
    bool comInitialized = false;
    int result = 0;
    try {
        Check(CoInitializeEx(nullptr,COINIT_MULTITHREADED)); comInitialized = true;
        Sample app;
        int argc = 0; LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(),&argc);
        if (!argv) throw std::runtime_error("Command-line parsing failed");
        for (int i = 1; i < argc; ++i) {
            if (std::wstring(argv[i]) == L"--warp") app.warp = true;
            else app.imageFile = argv[i];
        }
        LocalFree(argv);
        WNDCLASSW cls{}; cls.lpfnWndProc = WindowProc; cls.hInstance = instance;
        cls.lpszClassName = L"QubicTeachingWindow"; cls.hCursor = LoadCursor(nullptr,IDC_ARROW);
        if (!RegisterClassW(&cls)) throw std::runtime_error("Window class registration failed");
        RECT rectangle{0,0,LONG(app.width),LONG(app.height)};
        AdjustWindowRect(&rectangle,WS_OVERLAPPEDWINDOW,FALSE);
        app.window = CreateWindowExW(0,cls.lpszClassName,TEXTURED?L"Qubic DX12 — texture sample":L"Qubic DX12 — first triangle",
            WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,rectangle.right-rectangle.left,
            rectangle.bottom-rectangle.top,nullptr,nullptr,instance,&app);
        if (!app.window) throw std::runtime_error("Window creation failed");
        app.Init(); ShowWindow(app.window,show);
        MSG message{};
        while (message.message != WM_QUIT) {
            if (PeekMessageW(&message,nullptr,0,0,PM_REMOVE)) { TranslateMessage(&message); DispatchMessageW(&message); }
            else if (app.minimized) WaitMessage();
            else app.Render();
        }
        app.Flush();
    } catch (const std::exception& error) {
        MessageBoxA(nullptr,error.what(),"Qubic DX12 sample",MB_OK|MB_ICONERROR); result = 1;
    }
    if (comInitialized) CoUninitialize();
    return result;
}
