blob: c29ec6255c885fbb0a5e5cca8292952cd5725e4e [file] [edit]
/*
* Copyright 2026 Behdad Esfahbod. All Rights Reserved.
*/
#ifdef _WIN32
#include "demo-renderer.h"
#include "demo-atlas.h"
#include <d3d11.h>
#include <d3dcompiler.h>
#include <string>
#define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3.h>
#include <GLFW/glfw3native.h>
struct demo_renderer_d3d11_t : demo_renderer_t
{
GLFWwindow *window;
bool draw_only;
HWND hwnd;
ID3D11Device *device;
ID3D11DeviceContext *ctx;
IDXGISwapChain *swapchain;
ID3D11RenderTargetView *rtv;
ID3D11VertexShader *vs;
ID3D11PixelShader *ps;
ID3D11InputLayout *layout;
ID3D11Buffer *cbuf;
ID3D11Buffer *vbuf;
unsigned vbuf_capacity;
ID3D11BlendState *blend_state;
ID3D11RasterizerState *raster_state;
/* Atlas */
struct {
demo_atlas_t *obj;
ID3D11Buffer *srv_buf;
ID3D11ShaderResourceView *srv;
int32_t *shadow;
unsigned capacity;
unsigned cursor;
bool dirty;
} atlas;
/* State */
float bg_r, bg_g, bg_b, bg_a;
float fg_r, fg_g, fg_b, fg_a;
float gamma_val;
float stem_val;
float debug_val;
bool vsync_on;
int cur_width, cur_height;
/* Uniform layout (must match HLSL cbuffer) */
struct Uniforms {
float mvp[16];
float viewport[2];
float gamma;
float stem_darkening;
float foreground[4];
float debug;
float _pad[3];
};
demo_renderer_d3d11_t (GLFWwindow *window_, bool draw_only_)
: window (window_), draw_only (draw_only_)
{
hwnd = glfwGetWin32Window (window);
/* Create device + swap chain */
DXGI_SWAP_CHAIN_DESC scd = {};
scd.BufferCount = 1;
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
scd.OutputWindow = hwnd;
scd.SampleDesc.Count = 1;
scd.Windowed = TRUE;
D3D11CreateDeviceAndSwapChain (
nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0,
nullptr, 0, D3D11_SDK_VERSION,
&scd, &swapchain, &device, nullptr, &ctx);
/* Compile shaders */
#include "demo-shader-hlsl.hh"
std::string full;
if (draw_only)
full += "#define HB_GPU_DEMO_DRAW\n";
full += "StructuredBuffer<int4> hb_gpu_atlas : register(t0);\n";
full += hb_gpu_shader_source (HB_GPU_SHADER_STAGE_VERTEX, HB_GPU_SHADER_LANG_HLSL);
full += "\n";
full += hb_gpu_draw_shader_source (HB_GPU_SHADER_STAGE_VERTEX, HB_GPU_SHADER_LANG_HLSL);
full += "\n";
full += hb_gpu_shader_source (HB_GPU_SHADER_STAGE_FRAGMENT, HB_GPU_SHADER_LANG_HLSL);
full += "\n";
if (draw_only)
full += hb_gpu_draw_shader_source (HB_GPU_SHADER_STAGE_FRAGMENT, HB_GPU_SHADER_LANG_HLSL);
else
full += hb_gpu_paint_shader_source (HB_GPU_SHADER_STAGE_FRAGMENT, HB_GPU_SHADER_LANG_HLSL);
full += "\n";
full += demo_shader_hlsl;
auto compile = [&](const char *entry, const char *target) -> ID3DBlob* {
ID3DBlob *blob = nullptr, *errors = nullptr;
D3DCompile (full.c_str (), full.size (), nullptr, nullptr, nullptr,
entry, target, D3DCOMPILE_OPTIMIZATION_LEVEL3, 0, &blob, &errors);
if (errors) { fprintf (stderr, "%s\n", (char *) errors->GetBufferPointer ()); errors->Release (); }
return blob;
};
ID3DBlob *vs_blob = compile ("vs_main", "vs_5_0");
ID3DBlob *ps_blob = compile ("ps_main", "ps_5_0");
device->CreateVertexShader (vs_blob->GetBufferPointer (), vs_blob->GetBufferSize (), nullptr, &vs);
device->CreatePixelShader (ps_blob->GetBufferPointer (), ps_blob->GetBufferSize (), nullptr, &ps);
/* Input layout */
D3D11_INPUT_ELEMENT_DESC elems[] = {
{"POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, offsetof (glyph_vertex_t, x), D3D11_INPUT_PER_VERTEX_DATA, 0},
{"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, offsetof (glyph_vertex_t, tx), D3D11_INPUT_PER_VERTEX_DATA, 0},
{"NORMAL", 0, DXGI_FORMAT_R32G32_FLOAT, 0, offsetof (glyph_vertex_t, nx), D3D11_INPUT_PER_VERTEX_DATA, 0},
{"TEXCOORD", 1, DXGI_FORMAT_R32_FLOAT, 0, offsetof (glyph_vertex_t, emPerPos), D3D11_INPUT_PER_VERTEX_DATA, 0},
{"TEXCOORD", 2, DXGI_FORMAT_R32_UINT, 0, offsetof (glyph_vertex_t, atlas_offset), D3D11_INPUT_PER_VERTEX_DATA, 0},
};
device->CreateInputLayout (elems, 5, vs_blob->GetBufferPointer (), vs_blob->GetBufferSize (), &layout);
vs_blob->Release ();
ps_blob->Release ();
/* Constant buffer */
D3D11_BUFFER_DESC bd = {};
bd.ByteWidth = (sizeof (Uniforms) + 15) & ~15;
bd.Usage = D3D11_USAGE_DEFAULT;
bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
device->CreateBuffer (&bd, nullptr, &cbuf);
/* Atlas */
atlas.capacity = 256 * 1024;
atlas.cursor = 0;
atlas.dirty = true;
atlas.shadow = (int32_t *) calloc (atlas.capacity * 4, sizeof (int32_t));
{
D3D11_BUFFER_DESC abd = {};
abd.ByteWidth = atlas.capacity * 4 * sizeof (int32_t);
abd.Usage = D3D11_USAGE_DYNAMIC;
abd.BindFlags = D3D11_BIND_SHADER_RESOURCE;
abd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
abd.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
abd.StructureByteStride = 16;
device->CreateBuffer (&abd, nullptr, &atlas.srv_buf);
D3D11_SHADER_RESOURCE_VIEW_DESC srvd = {};
srvd.Format = DXGI_FORMAT_UNKNOWN;
srvd.ViewDimension = D3D11_SRV_DIMENSION_BUFFER;
srvd.Buffer.NumElements = atlas.capacity;
device->CreateShaderResourceView (atlas.srv_buf, &srvd, &atlas.srv);
}
/* Atlas backend callbacks */
demo_atlas_backend_t backend = {};
backend.ctx = this;
backend.alloc = [](void *ctx_, const char *data, unsigned len_bytes) -> unsigned {
auto *self = (demo_renderer_d3d11_t *) ctx_;
unsigned len_texels = len_bytes / 8;
unsigned offset = self->atlas.cursor;
self->atlas.cursor += len_texels;
const int16_t *src = (const int16_t *) data;
int32_t *dst = self->atlas.shadow + offset * 4;
for (unsigned i = 0; i < len_texels * 4; i++)
dst[i] = (int32_t) src[i];
self->atlas.dirty = true;
return offset;
};
backend.get_used = [](void *ctx_) -> unsigned {
return ((demo_renderer_d3d11_t *) ctx_)->atlas.cursor;
};
backend.clear = [](void *ctx_) {
auto *self = (demo_renderer_d3d11_t *) ctx_;
self->atlas.cursor = 0;
self->atlas.dirty = true;
};
atlas.obj = demo_atlas_create_external (&backend);
/* Blend state */
D3D11_BLEND_DESC bld = {};
bld.RenderTarget[0].BlendEnable = TRUE;
bld.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
bld.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
bld.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
bld.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
bld.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;
bld.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
bld.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
device->CreateBlendState (&bld, &blend_state);
/* Rasterizer state (no culling) */
D3D11_RASTERIZER_DESC rd = {};
rd.FillMode = D3D11_FILL_SOLID;
rd.CullMode = D3D11_CULL_NONE;
device->CreateRasterizerState (&rd, &raster_state);
ctx->RSSetState (raster_state);
/* Initial state */
bg_r = bg_g = bg_b = 1; bg_a = 1;
fg_r = fg_g = fg_b = 0; fg_a = 1;
gamma_val = 1;
stem_val = 1;
debug_val = 0;
vsync_on = true;
vbuf = nullptr;
vbuf_capacity = 0;
rtv = nullptr;
cur_width = cur_height = 0;
}
~demo_renderer_d3d11_t () override
{
if (atlas.obj) demo_atlas_destroy (atlas.obj);
free (atlas.shadow);
}
demo_atlas_t *get_atlas () override { return atlas.obj; }
void setup () override { gamma_val = 1.f / 2.2f; stem_val = 1; }
void set_gamma (float g) override { gamma_val = g; }
void set_foreground (float r, float g, float b, float a) override { fg_r = r; fg_g = g; fg_b = b; fg_a = a; }
void set_background (float r, float g, float b, float a) override { bg_r = r; bg_g = g; bg_b = b; bg_a = a; }
void set_debug (bool e) override { debug_val = e ? 1.f : 0.f; }
void set_stem_darkening (bool e) override { stem_val = e ? 1.f : 0.f; }
void toggle_vsync (bool &v) override { v = !v; vsync_on = v; }
void display (glyph_vertex_t *vertices, unsigned count,
unsigned generation,
int width, int height, float mat[16]) override
{
if (width != cur_width || height != cur_height)
{
cur_width = width; cur_height = height;
if (rtv) { rtv->Release (); rtv = nullptr; }
swapchain->ResizeBuffers (0, width, height, DXGI_FORMAT_UNKNOWN, 0);
ID3D11Texture2D *backbuf;
swapchain->GetBuffer (0, __uuidof (ID3D11Texture2D), (void **) &backbuf);
device->CreateRenderTargetView (backbuf, nullptr, &rtv);
backbuf->Release ();
}
/* Upload atlas */
if (atlas.dirty && atlas.cursor > 0)
{
D3D11_MAPPED_SUBRESOURCE mapped;
ctx->Map (atlas.srv_buf, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped);
memcpy (mapped.pData, atlas.shadow, atlas.cursor * 4 * sizeof (int32_t));
ctx->Unmap (atlas.srv_buf, 0);
atlas.dirty = false;
}
/* Upload vertices */
if (count > 0)
{
unsigned needed = count * sizeof (glyph_vertex_t);
if (needed > vbuf_capacity)
{
if (vbuf) vbuf->Release ();
D3D11_BUFFER_DESC bd = {};
bd.ByteWidth = needed;
bd.Usage = D3D11_USAGE_DYNAMIC;
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
device->CreateBuffer (&bd, nullptr, &vbuf);
vbuf_capacity = needed;
}
D3D11_MAPPED_SUBRESOURCE mapped;
ctx->Map (vbuf, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped);
memcpy (mapped.pData, vertices, needed);
ctx->Unmap (vbuf, 0);
}
/* Uniforms */
Uniforms u;
memcpy (u.mvp, mat, sizeof (u.mvp));
u.viewport[0] = (float) width;
u.viewport[1] = (float) height;
u.gamma = gamma_val;
u.stem_darkening = stem_val;
u.foreground[0] = fg_r; u.foreground[1] = fg_g;
u.foreground[2] = fg_b; u.foreground[3] = fg_a;
u.debug = debug_val;
ctx->UpdateSubresource (cbuf, 0, nullptr, &u, 0, 0);
/* Render */
D3D11_VIEWPORT vp = {0, 0, (float) width, (float) height, 0, 1};
ctx->RSSetViewports (1, &vp);
ctx->OMSetRenderTargets (1, &rtv, nullptr);
float clear[] = {bg_r, bg_g, bg_b, bg_a};
ctx->ClearRenderTargetView (rtv, clear);
if (count > 0)
{
ctx->VSSetShader (vs, nullptr, 0);
ctx->PSSetShader (ps, nullptr, 0);
ctx->VSSetConstantBuffers (0, 1, &cbuf);
ctx->PSSetConstantBuffers (0, 1, &cbuf);
ctx->PSSetShaderResources (0, 1, &atlas.srv);
ctx->OMSetBlendState (blend_state, nullptr, 0xffffffff);
UINT stride = sizeof (glyph_vertex_t), offset = 0;
ctx->IASetVertexBuffers (0, 1, &vbuf, &stride, &offset);
ctx->IASetPrimitiveTopology (D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
ctx->IASetInputLayout (layout);
ctx->Draw (count, 0);
}
swapchain->Present (vsync_on ? 1 : 0, 0);
}
};
demo_renderer_t *
demo_renderer_create_d3d11 (GLFWwindow *window, bool draw_only)
{
return new demo_renderer_d3d11_t (window, draw_only);
}
#endif /* _WIN32 */