864 lines
30 KiB
Zig
864 lines
30 KiB
Zig
const builtin = @import("builtin");
|
|
const std = @import("std");
|
|
const win32 = @import("win32").everything;
|
|
const win32ext = @import("win32ext.zig");
|
|
|
|
const dwrite = @import("dwrite.zig");
|
|
const GlyphIndexCache = @import("GlyphIndexCache.zig");
|
|
const TextRenderer = @import("DwriteRenderer.zig");
|
|
|
|
const XY = @import("xy.zig").XY;
|
|
|
|
pub const Font = TextRenderer.Font;
|
|
pub const Fonts = TextRenderer.Fonts;
|
|
|
|
const log = std.log.scoped(.d3d);
|
|
|
|
// the redirection bitmap is unnecessary for a d3d window and causes
|
|
// bad artifacts when the window is resized
|
|
pub const NOREDIRECTIONBITMAP = 1;
|
|
|
|
const D2dFactory = if (TextRenderer.needs_direct2d) *win32.ID2D1Factory else void;
|
|
|
|
const global = struct {
|
|
var init_called: bool = false;
|
|
var d3d: D3d = undefined;
|
|
var shaders: Shaders = undefined;
|
|
var const_buf: *win32.ID3D11Buffer = undefined;
|
|
var d2d_factory: D2dFactory = undefined;
|
|
var glyph_cache_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
|
var staging_texture: StagingTexture = .{};
|
|
var background: Rgba8 = .{ .r = 19, .g = 19, .b = 19, .a = 255 };
|
|
};
|
|
|
|
pub const Color = Rgba8;
|
|
const Rgba8 = packed struct(u32) {
|
|
a: u8,
|
|
b: u8,
|
|
g: u8,
|
|
r: u8,
|
|
pub fn initRgb(r: u8, g: u8, b: u8) Color {
|
|
return .{ .r = r, .g = g, .b = b, .a = 255 };
|
|
}
|
|
pub fn initRgba(r: u8, g: u8, b: u8, a: u8) Color {
|
|
return .{ .r = r, .g = g, .b = b, .a = a };
|
|
}
|
|
};
|
|
|
|
pub const Cell = shader.Cell;
|
|
|
|
// types shared with the shader
|
|
pub const shader = struct {
|
|
const GridConfig = extern struct {
|
|
cell_size: [2]u32,
|
|
col_count: u32,
|
|
row_count: u32,
|
|
};
|
|
pub const Cell = extern struct {
|
|
glyph_index: u32,
|
|
background: Rgba8,
|
|
foreground: Rgba8,
|
|
};
|
|
};
|
|
|
|
const swap_chain_flags: u32 = @intFromEnum(win32.DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT);
|
|
|
|
pub fn init(opt: struct {
|
|
shader: ?[:0]const u8 = null,
|
|
}) void {
|
|
std.debug.assert(!global.init_called);
|
|
global.init_called = true;
|
|
dwrite.init();
|
|
|
|
const try_debug = switch (builtin.mode) {
|
|
.Debug => true,
|
|
else => false,
|
|
};
|
|
|
|
global.d3d, const debug = D3d.init(.{ .try_debug = try_debug });
|
|
|
|
if (debug) {
|
|
const info = win32ext.queryInterface(global.d3d.device, win32.ID3D11InfoQueue);
|
|
defer _ = info.IUnknown.Release();
|
|
{
|
|
const hr = info.SetBreakOnSeverity(.CORRUPTION, 1);
|
|
if (hr < 0) fatalHr("SetBreakOnCorruption", hr);
|
|
}
|
|
{
|
|
const hr = info.SetBreakOnSeverity(.ERROR, 1);
|
|
if (hr < 0) fatalHr("SetBreakOnError", hr);
|
|
}
|
|
{
|
|
const hr = info.SetBreakOnSeverity(.WARNING, 1);
|
|
if (hr < 0) fatalHr("SetBreakOnWarning", hr);
|
|
}
|
|
}
|
|
|
|
global.shaders = Shaders.init(opt.shader);
|
|
|
|
{
|
|
const desc: win32.D3D11_BUFFER_DESC = .{
|
|
// d3d requires constants be sized in multiples of 16
|
|
.ByteWidth = std.mem.alignForward(u32, @sizeOf(shader.GridConfig), 16),
|
|
.Usage = .DYNAMIC,
|
|
.BindFlags = .{ .CONSTANT_BUFFER = 1 },
|
|
.CPUAccessFlags = .{ .WRITE = 1 },
|
|
.MiscFlags = .{},
|
|
.StructureByteStride = 0,
|
|
};
|
|
const hr = global.d3d.device.CreateBuffer(&desc, null, &global.const_buf);
|
|
if (hr < 0) fatalHr("CreateBuffer for grid config", hr);
|
|
}
|
|
|
|
if (TextRenderer.needs_direct2d) {
|
|
const hr = win32.D2D1CreateFactory(
|
|
.SINGLE_THREADED,
|
|
win32.IID_ID2D1Factory,
|
|
null,
|
|
@ptrCast(&global.d2d_factory),
|
|
);
|
|
if (hr < 0) fatalHr("D2D1CreateFactory", hr);
|
|
}
|
|
}
|
|
|
|
pub fn setBackground(state: *const WindowState, c: Color) void {
|
|
global.background = c;
|
|
const color: win32.DXGI_RGBA = .{
|
|
.r = @as(f32, @floatFromInt(c.r)) / 255,
|
|
.g = @as(f32, @floatFromInt(c.g)) / 255,
|
|
.b = @as(f32, @floatFromInt(c.b)) / 255,
|
|
.a = 1.0,
|
|
};
|
|
const hr = state.swap_chain.IDXGISwapChain1.SetBackgroundColor(&color);
|
|
if (hr < 0) fatalHr("SetBackgroundColor", hr);
|
|
}
|
|
|
|
pub const WindowState = struct {
|
|
swap_chain: *win32.IDXGISwapChain2,
|
|
maybe_target_view: ?*win32.ID3D11RenderTargetView = null,
|
|
shader_cells: ShaderCells = .{},
|
|
|
|
glyph_texture: GlyphTexture = .{},
|
|
glyph_cache_cell_size: ?XY(u16) = null,
|
|
glyph_index_cache: ?GlyphIndexCache = null,
|
|
|
|
pub fn init(hwnd: win32.HWND) WindowState {
|
|
std.debug.assert(global.init_called);
|
|
const swap_chain = initSwapChain(global.d3d.device, hwnd);
|
|
return .{ .swap_chain = swap_chain };
|
|
}
|
|
|
|
// TODO: this should take a utf8 graphme instead
|
|
pub fn generateGlyph(state: *WindowState, font: Font, codepoint: u21, right_half: bool) u32 {
|
|
// for now we'll just use 1 texture and leverage the entire thing
|
|
const texture_cell_count: XY(u16) = getD3d11TextureMaxCellCount(font.cell_size);
|
|
const texture_cell_count_total: u32 =
|
|
@as(u32, texture_cell_count.x) * @as(u32, texture_cell_count.y);
|
|
|
|
const texture_pixel_size: XY(u16) = .{
|
|
.x = texture_cell_count.x * font.cell_size.x,
|
|
.y = texture_cell_count.y * font.cell_size.y,
|
|
};
|
|
const texture_retained = switch (state.glyph_texture.updateSize(texture_pixel_size)) {
|
|
.retained => true,
|
|
.newly_created => false,
|
|
};
|
|
|
|
const cache_cell_size_valid = if (state.glyph_cache_cell_size) |size| size.eql(font.cell_size) else false;
|
|
state.glyph_cache_cell_size = font.cell_size;
|
|
|
|
if (!texture_retained or !cache_cell_size_valid) {
|
|
if (state.glyph_index_cache) |*c| {
|
|
c.deinit(global.glyph_cache_arena.allocator());
|
|
_ = global.glyph_cache_arena.reset(.retain_capacity);
|
|
state.glyph_index_cache = null;
|
|
}
|
|
}
|
|
|
|
const glyph_index_cache = blk: {
|
|
if (state.glyph_index_cache) |*c| break :blk c;
|
|
state.glyph_index_cache = GlyphIndexCache.init(
|
|
global.glyph_cache_arena.allocator(),
|
|
texture_cell_count_total,
|
|
) catch |e| oom(e);
|
|
break :blk &(state.glyph_index_cache.?);
|
|
};
|
|
|
|
switch (glyph_index_cache.reserve(
|
|
global.glyph_cache_arena.allocator(),
|
|
codepoint,
|
|
right_half,
|
|
) catch |e| oom(e)) {
|
|
.newly_reserved => |reserved| {
|
|
// var render_success = false;
|
|
// defer if (!render_success) state.glyph_index_cache.remove(reserved.index);
|
|
const pos: XY(u16) = cellPosFromIndex(reserved.index, texture_cell_count.x);
|
|
const coord = coordFromCellPos(font.cell_size, pos);
|
|
var staging_size = font.cell_size;
|
|
staging_size.x = if (right_half) staging_size.x * 2 else staging_size.x;
|
|
const staging = global.staging_texture.update(staging_size);
|
|
var utf8_buf: [7]u8 = undefined;
|
|
const utf8_len: u3 = std.unicode.utf8Encode(codepoint, &utf8_buf) catch |e| std.debug.panic(
|
|
"todo: handle invalid codepoint {} (0x{0x}) ({s})",
|
|
.{ codepoint, @errorName(e) },
|
|
);
|
|
staging.text_renderer.render(
|
|
font,
|
|
utf8_buf[0..utf8_len],
|
|
);
|
|
const box: win32.D3D11_BOX = .{
|
|
.left = if (right_half) font.cell_size.x else 0,
|
|
.top = 0,
|
|
.front = 0,
|
|
.right = if (right_half) font.cell_size.x * 2 else font.cell_size.x,
|
|
.bottom = font.cell_size.y,
|
|
.back = 1,
|
|
};
|
|
|
|
global.d3d.context.CopySubresourceRegion(
|
|
&state.glyph_texture.obj.ID3D11Resource,
|
|
0, // subresource
|
|
coord.x,
|
|
coord.y,
|
|
0, // z
|
|
&staging.texture.ID3D11Resource,
|
|
0, // subresource
|
|
&box,
|
|
);
|
|
return reserved.index;
|
|
},
|
|
.already_reserved => |index| return index,
|
|
}
|
|
}
|
|
};
|
|
|
|
pub fn paint(
|
|
state: *WindowState,
|
|
client_size: XY(u32),
|
|
font: Font,
|
|
row_count: u16,
|
|
col_count: u16,
|
|
top: u16,
|
|
cells: []const Cell,
|
|
) void {
|
|
{
|
|
const swap_chain_size = getSwapChainSize(state.swap_chain);
|
|
if (swap_chain_size.x != client_size.x or swap_chain_size.y != client_size.y) {
|
|
log.debug(
|
|
"SwapChain Buffer Resize from {}x{} to {}x{}",
|
|
.{ swap_chain_size.x, swap_chain_size.y, client_size.x, client_size.y },
|
|
);
|
|
global.d3d.context.ClearState();
|
|
if (state.maybe_target_view) |target_view| {
|
|
_ = target_view.IUnknown.Release();
|
|
state.maybe_target_view = null;
|
|
}
|
|
global.d3d.context.Flush();
|
|
if (swap_chain_size.x == 0) @panic("possible? no need to resize?");
|
|
if (swap_chain_size.y == 0) @panic("possible? no need to resize?");
|
|
|
|
{
|
|
const hr = state.swap_chain.IDXGISwapChain.ResizeBuffers(
|
|
0,
|
|
client_size.x,
|
|
client_size.y,
|
|
.UNKNOWN,
|
|
swap_chain_flags,
|
|
);
|
|
if (hr < 0) fatalHr("ResizeBuffers", hr);
|
|
}
|
|
}
|
|
}
|
|
|
|
const shader_col_count: u16 = @intCast(@divTrunc(client_size.x + font.cell_size.x - 1, font.cell_size.x));
|
|
const shader_row_count: u16 = @intCast(@divTrunc(client_size.y + font.cell_size.y - 1, font.cell_size.y));
|
|
|
|
{
|
|
var mapped: win32.D3D11_MAPPED_SUBRESOURCE = undefined;
|
|
const hr = global.d3d.context.Map(
|
|
&global.const_buf.ID3D11Resource,
|
|
0,
|
|
.WRITE_DISCARD,
|
|
0,
|
|
&mapped,
|
|
);
|
|
if (hr < 0) fatalHr("MapConstBuffer", hr);
|
|
defer global.d3d.context.Unmap(&global.const_buf.ID3D11Resource, 0);
|
|
const config: *shader.GridConfig = @ptrCast(@alignCast(mapped.pData));
|
|
config.cell_size[0] = font.cell_size.x;
|
|
config.cell_size[1] = font.cell_size.y;
|
|
config.col_count = shader_col_count;
|
|
config.row_count = shader_row_count;
|
|
}
|
|
|
|
const copy_col_count: u16 = @min(col_count, shader_col_count);
|
|
const blank_space_glyph_index = state.generateGlyph(font, ' ', false);
|
|
|
|
const cell_count: u32 = @as(u32, shader_col_count) * @as(u32, shader_row_count);
|
|
state.shader_cells.updateCount(cell_count);
|
|
if (state.shader_cells.count > 0) {
|
|
var mapped: win32.D3D11_MAPPED_SUBRESOURCE = undefined;
|
|
const hr = global.d3d.context.Map(
|
|
&state.shader_cells.cell_buf.ID3D11Resource,
|
|
0,
|
|
.WRITE_DISCARD,
|
|
0,
|
|
&mapped,
|
|
);
|
|
if (hr < 0) fatalHr("MapCellBuffer", hr);
|
|
defer global.d3d.context.Unmap(&state.shader_cells.cell_buf.ID3D11Resource, 0);
|
|
|
|
const cells_shader: [*]shader.Cell = @ptrCast(@alignCast(mapped.pData));
|
|
for (0..shader_row_count) |row| {
|
|
const src_row = blk: {
|
|
const r = top + row;
|
|
break :blk if (r < row_count) r else 0;
|
|
};
|
|
const src_row_offset = src_row * col_count;
|
|
const dst_row_offset = row * shader_col_count;
|
|
const copy_len = if (row < row_count) copy_col_count else 0;
|
|
@memcpy(
|
|
cells_shader[dst_row_offset..][0..copy_len],
|
|
cells[src_row_offset..][0..copy_len],
|
|
);
|
|
@memset(cells_shader[dst_row_offset..][copy_len..shader_col_count], .{
|
|
.glyph_index = blank_space_glyph_index,
|
|
.background = global.background,
|
|
.foreground = global.background,
|
|
});
|
|
}
|
|
}
|
|
|
|
if (state.maybe_target_view == null) {
|
|
state.maybe_target_view = createRenderTargetView(
|
|
global.d3d.device,
|
|
&state.swap_chain.IDXGISwapChain,
|
|
client_size,
|
|
);
|
|
}
|
|
{
|
|
var target_views = [_]?*win32.ID3D11RenderTargetView{state.maybe_target_view.?};
|
|
global.d3d.context.OMSetRenderTargets(target_views.len, &target_views, null);
|
|
}
|
|
|
|
global.d3d.context.PSSetConstantBuffers(0, 1, @constCast(@ptrCast(&global.const_buf)));
|
|
var resources = [_]?*win32.ID3D11ShaderResourceView{
|
|
if (state.shader_cells.count > 0) state.shader_cells.cell_view else null,
|
|
state.glyph_texture.view,
|
|
};
|
|
global.d3d.context.PSSetShaderResources(0, resources.len, &resources);
|
|
global.d3d.context.VSSetShader(global.shaders.vertex, null, 0);
|
|
global.d3d.context.PSSetShader(global.shaders.pixel, null, 0);
|
|
global.d3d.context.Draw(4, 0);
|
|
|
|
// NOTE: don't enable vsync, it causes the gpu to lag behind horribly
|
|
// if we flood it with resize events
|
|
{
|
|
const hr = state.swap_chain.IDXGISwapChain.Present(0, 0);
|
|
if (hr < 0) fatalHr("SwapChainPresent", hr);
|
|
}
|
|
}
|
|
|
|
const D3d = struct {
|
|
device: *win32.ID3D11Device,
|
|
context: *win32.ID3D11DeviceContext,
|
|
context1: *win32.ID3D11DeviceContext1,
|
|
|
|
pub fn init(opt: struct { try_debug: bool }) struct { D3d, bool } {
|
|
const levels = [_]win32.D3D_FEATURE_LEVEL{
|
|
.@"11_0",
|
|
};
|
|
var last_hr: i32 = undefined;
|
|
|
|
const Config = struct {
|
|
driver: win32.D3D_DRIVER_TYPE,
|
|
debug: bool,
|
|
};
|
|
const configs = [_]Config{
|
|
.{ .driver = .HARDWARE, .debug = true },
|
|
.{ .driver = .HARDWARE, .debug = false },
|
|
.{ .driver = .SOFTWARE, .debug = true },
|
|
.{ .driver = .SOFTWARE, .debug = false },
|
|
};
|
|
|
|
for (configs) |config| {
|
|
const skip_config = config.debug and !opt.try_debug;
|
|
if (skip_config) continue;
|
|
|
|
var device: *win32.ID3D11Device = undefined;
|
|
var context: *win32.ID3D11DeviceContext = undefined;
|
|
last_hr = win32.D3D11CreateDevice(
|
|
null,
|
|
config.driver,
|
|
null,
|
|
.{
|
|
.BGRA_SUPPORT = 1,
|
|
.SINGLETHREADED = 1,
|
|
.DEBUG = if (config.debug) 1 else 0,
|
|
},
|
|
&levels,
|
|
levels.len,
|
|
win32.D3D11_SDK_VERSION,
|
|
&device,
|
|
null,
|
|
&context,
|
|
);
|
|
if (last_hr >= 0) {
|
|
std.log.info("d3d11: {s} debug={}", .{ @tagName(config.driver), config.debug });
|
|
return .{
|
|
.{
|
|
.device = device,
|
|
.context = context,
|
|
.context1 = win32ext.queryInterface(context, win32.ID3D11DeviceContext1),
|
|
},
|
|
config.debug,
|
|
};
|
|
}
|
|
std.log.info(
|
|
"D3D11 {s} Driver (with{s} debug) error, hresult=0x{x}",
|
|
.{ @tagName(config.driver), if (config.debug) "" else "out", @as(u32, @bitCast(last_hr)) },
|
|
);
|
|
}
|
|
std.debug.panic("failed to initialize Direct3D11, hresult=0x{x}", .{@as(u32, @bitCast(last_hr))});
|
|
}
|
|
};
|
|
|
|
fn getDxgiFactory(device: *win32.ID3D11Device) *win32.IDXGIFactory2 {
|
|
const dxgi_device = win32ext.queryInterface(device, win32.IDXGIDevice);
|
|
defer _ = dxgi_device.IUnknown.Release();
|
|
|
|
var adapter: *win32.IDXGIAdapter = undefined;
|
|
{
|
|
const hr = dxgi_device.GetAdapter(&adapter);
|
|
if (hr < 0) fatalHr("GetDxgiAdapter", hr);
|
|
}
|
|
defer _ = adapter.IUnknown.Release();
|
|
|
|
var factory: *win32.IDXGIFactory2 = undefined;
|
|
{
|
|
const hr = adapter.IDXGIObject.GetParent(win32.IID_IDXGIFactory2, @ptrCast(&factory));
|
|
if (hr < 0) fatalHr("GetDxgiFactory", hr);
|
|
}
|
|
return factory;
|
|
}
|
|
|
|
fn getSwapChainSize(swap_chain: *win32.IDXGISwapChain2) XY(u32) {
|
|
var size: XY(u32) = undefined;
|
|
{
|
|
const hr = swap_chain.GetSourceSize(&size.x, &size.y);
|
|
if (hr < 0) fatalHr("GetSwapChainSourceSize", hr);
|
|
}
|
|
return size;
|
|
}
|
|
|
|
fn initSwapChain(
|
|
device: *win32.ID3D11Device,
|
|
hwnd: win32.HWND,
|
|
) *win32.IDXGISwapChain2 {
|
|
const factory = getDxgiFactory(device);
|
|
defer _ = factory.IUnknown.Release();
|
|
|
|
const swap_chain1: *win32.IDXGISwapChain1 = blk: {
|
|
var swap_chain1: *win32.IDXGISwapChain1 = undefined;
|
|
const desc = win32.DXGI_SWAP_CHAIN_DESC1{
|
|
.Width = 0,
|
|
.Height = 0,
|
|
.Format = .B8G8R8A8_UNORM,
|
|
.Stereo = 0,
|
|
.SampleDesc = .{ .Count = 1, .Quality = 0 },
|
|
.BufferUsage = win32.DXGI_USAGE_RENDER_TARGET_OUTPUT,
|
|
.BufferCount = 2,
|
|
.Scaling = .NONE,
|
|
.SwapEffect = .FLIP_DISCARD,
|
|
.AlphaMode = .IGNORE,
|
|
.Flags = swap_chain_flags,
|
|
};
|
|
{
|
|
const hr = factory.CreateSwapChainForHwnd(
|
|
&device.IUnknown,
|
|
hwnd,
|
|
&desc,
|
|
null,
|
|
null,
|
|
&swap_chain1,
|
|
);
|
|
if (hr < 0) fatalHr("CreateD3dSwapChain", hr);
|
|
}
|
|
break :blk swap_chain1;
|
|
};
|
|
defer _ = swap_chain1.IUnknown.Release();
|
|
|
|
{
|
|
const color: win32.DXGI_RGBA = .{ .r = 0.075, .g = 0.075, .b = 0.075, .a = 1.0 };
|
|
const hr = swap_chain1.SetBackgroundColor(&color);
|
|
if (hr < 0) fatalHr("SetBackgroundColor", hr);
|
|
}
|
|
|
|
var swap_chain2: *win32.IDXGISwapChain2 = undefined;
|
|
{
|
|
const hr = swap_chain1.IUnknown.QueryInterface(win32.IID_IDXGISwapChain2, @ptrCast(&swap_chain2));
|
|
if (hr < 0) fatalHr("QuerySwapChain2", hr);
|
|
}
|
|
|
|
// refterm is doing this but I don't know why
|
|
if (false) {
|
|
const hr = factory.IDXGIFactory.MakeWindowAssociation(hwnd, 0); //DXGI_MWA_NO_ALT_ENTER | DXGI_MWA_NO_WINDOW_CHANGES);
|
|
if (hr < 0) fatalHr("MakeWindowAssoc", hr);
|
|
}
|
|
|
|
return swap_chain2;
|
|
}
|
|
|
|
const Shaders = struct {
|
|
vertex: *win32.ID3D11VertexShader,
|
|
pixel: *win32.ID3D11PixelShader,
|
|
pub fn init(maybe_file_path: ?[:0]const u8) Shaders {
|
|
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
|
defer arena.deinit();
|
|
const shader_source: []const u8 = blk: {
|
|
if (maybe_file_path) |file_path| {
|
|
var file = std.fs.cwd().openFileZ(file_path, .{}) catch |e| std.debug.panic(
|
|
"failed to open --shader '{s}' with {s}",
|
|
.{ file_path, @errorName(e) },
|
|
);
|
|
defer file.close();
|
|
break :blk file.readToEndAlloc(arena.allocator(), std.math.maxInt(usize)) catch |e| std.debug.panic(
|
|
"read --shader '{s}' failed with {s}",
|
|
.{ file_path, @errorName(e) },
|
|
);
|
|
}
|
|
break :blk @embedFile("builtin.hlsl");
|
|
};
|
|
const file = maybe_file_path orelse "builtin.hlsl";
|
|
|
|
var vs_blob: *win32.ID3DBlob = undefined;
|
|
var error_blob: ?*win32.ID3DBlob = null;
|
|
{
|
|
const hr = win32.D3DCompile(
|
|
shader_source.ptr,
|
|
shader_source.len,
|
|
file,
|
|
null,
|
|
null,
|
|
"VertexMain",
|
|
"vs_5_0",
|
|
0,
|
|
0,
|
|
@ptrCast(&vs_blob),
|
|
@ptrCast(&error_blob),
|
|
);
|
|
reportShaderError(.vertex, error_blob);
|
|
error_blob = null;
|
|
if (hr < 0) {
|
|
fatalHr("D3DCompileVertexShader", hr);
|
|
}
|
|
}
|
|
defer _ = vs_blob.IUnknown.Release();
|
|
var ps_blob: *win32.ID3DBlob = undefined;
|
|
{
|
|
const hr = win32.D3DCompile(
|
|
shader_source.ptr,
|
|
shader_source.len,
|
|
file,
|
|
null,
|
|
null,
|
|
"PixelMain",
|
|
"ps_5_0",
|
|
0,
|
|
0,
|
|
@ptrCast(&ps_blob),
|
|
@ptrCast(&error_blob),
|
|
);
|
|
reportShaderError(.pixel, error_blob);
|
|
error_blob = null;
|
|
if (hr < 0) {
|
|
fatalHr("D3DCompilePixelShader", hr);
|
|
}
|
|
}
|
|
defer _ = ps_blob.IUnknown.Release();
|
|
|
|
var vertex_shader: *win32.ID3D11VertexShader = undefined;
|
|
{
|
|
const hr = global.d3d.device.CreateVertexShader(
|
|
@ptrCast(vs_blob.GetBufferPointer()),
|
|
vs_blob.GetBufferSize(),
|
|
null,
|
|
&vertex_shader,
|
|
);
|
|
if (hr < 0) fatalHr("CreateVertexShader", hr);
|
|
}
|
|
errdefer vertex_shader.IUnknown.Release();
|
|
|
|
var pixel_shader: *win32.ID3D11PixelShader = undefined;
|
|
{
|
|
const hr = global.d3d.device.CreatePixelShader(
|
|
@ptrCast(ps_blob.GetBufferPointer()),
|
|
ps_blob.GetBufferSize(),
|
|
null,
|
|
&pixel_shader,
|
|
);
|
|
if (hr < 0) fatalHr("CreatePixelShader", hr);
|
|
}
|
|
errdefer pixel_shader.IUnknown.Release();
|
|
|
|
return .{
|
|
.vertex = vertex_shader,
|
|
.pixel = pixel_shader,
|
|
};
|
|
}
|
|
};
|
|
fn reportShaderError(kind: enum { vertex, pixel }, maybe_error_blob: ?*win32.ID3DBlob) void {
|
|
const err = maybe_error_blob orelse return;
|
|
defer _ = err.IUnknown.Release();
|
|
const ptr: [*]const u8 = @ptrCast(err.GetBufferPointer() orelse return);
|
|
const str = ptr[0..err.GetBufferSize()];
|
|
log.err("{s} shader error:\n{s}\n", .{ @tagName(kind), str });
|
|
std.debug.panic("{s} shader error:\n{s}\n", .{ @tagName(kind), str });
|
|
}
|
|
|
|
const ShaderCells = struct {
|
|
count: u32 = 0,
|
|
cell_buf: *win32.ID3D11Buffer = undefined,
|
|
cell_view: *win32.ID3D11ShaderResourceView = undefined,
|
|
pub fn updateCount(self: *ShaderCells, count: u32) void {
|
|
if (count == self.count) return;
|
|
|
|
log.debug("CellCount {} > {}", .{ self.count, count });
|
|
if (self.count != 0) {
|
|
_ = self.cell_view.IUnknown.Release();
|
|
_ = self.cell_buf.IUnknown.Release();
|
|
self.count = 0;
|
|
}
|
|
|
|
if (count > 0) {
|
|
self.cell_buf = createCellBuffer(global.d3d.device, count);
|
|
errdefer {
|
|
self.cell_buf.IUnknown.Release();
|
|
self.cell_buf = undefined;
|
|
}
|
|
|
|
{
|
|
const desc: win32.D3D11_SHADER_RESOURCE_VIEW_DESC = .{
|
|
.Format = .UNKNOWN,
|
|
.ViewDimension = ._SRV_DIMENSION_BUFFER,
|
|
.Anonymous = .{
|
|
.Buffer = .{
|
|
.Anonymous1 = .{ .FirstElement = 0 },
|
|
.Anonymous2 = .{ .NumElements = count },
|
|
},
|
|
},
|
|
};
|
|
const hr = global.d3d.device.CreateShaderResourceView(
|
|
&self.cell_buf.ID3D11Resource,
|
|
&desc,
|
|
&self.cell_view,
|
|
);
|
|
if (hr < 0) fatalHr("CreateShaderResourceView for cells", hr);
|
|
}
|
|
}
|
|
self.count = count;
|
|
}
|
|
};
|
|
|
|
const GlyphTexture = struct {
|
|
size: ?XY(u16) = null,
|
|
obj: *win32.ID3D11Texture2D = undefined,
|
|
view: *win32.ID3D11ShaderResourceView = undefined,
|
|
pub fn updateSize(self: *GlyphTexture, size: XY(u16)) enum { retained, newly_created } {
|
|
if (self.size) |existing_size| {
|
|
if (existing_size.eql(size)) return .retained;
|
|
|
|
_ = self.view.IUnknown.Release();
|
|
self.view = undefined;
|
|
_ = self.obj.IUnknown.Release();
|
|
self.obj = undefined;
|
|
self.size = null;
|
|
}
|
|
log.debug("GlyphTexture: init {}x{}", .{ size.x, size.y });
|
|
|
|
{
|
|
const desc: win32.D3D11_TEXTURE2D_DESC = .{
|
|
.Width = size.x,
|
|
.Height = size.y,
|
|
.MipLevels = 1,
|
|
.ArraySize = 1,
|
|
.Format = .A8_UNORM,
|
|
.SampleDesc = .{ .Count = 1, .Quality = 0 },
|
|
.Usage = .DEFAULT,
|
|
.BindFlags = .{ .SHADER_RESOURCE = 1 },
|
|
.CPUAccessFlags = .{},
|
|
.MiscFlags = .{},
|
|
};
|
|
const hr = global.d3d.device.CreateTexture2D(&desc, null, &self.obj);
|
|
if (hr < 0) fatalHr("CreateGlyphTexture", hr);
|
|
}
|
|
errdefer {
|
|
self.obj.IUnknown.Release();
|
|
self.obj = undefined;
|
|
}
|
|
|
|
{
|
|
const hr = global.d3d.device.CreateShaderResourceView(
|
|
&self.obj.ID3D11Resource,
|
|
null,
|
|
&self.view,
|
|
);
|
|
if (hr < 0) fatalHr("CreateGlyphView", hr);
|
|
}
|
|
self.size = size;
|
|
return .newly_created;
|
|
}
|
|
};
|
|
|
|
const StagingTexture = struct {
|
|
const Cached = struct {
|
|
size: XY(u16),
|
|
texture: *win32.ID3D11Texture2D,
|
|
text_renderer: TextRenderer,
|
|
};
|
|
cached: ?Cached = null,
|
|
pub fn update(
|
|
self: *StagingTexture,
|
|
size: XY(u16),
|
|
) struct {
|
|
texture: *win32.ID3D11Texture2D,
|
|
text_renderer: TextRenderer,
|
|
} {
|
|
if (self.cached) |*cached| {
|
|
if (cached.size.eql(size)) return .{
|
|
.texture = cached.texture,
|
|
.text_renderer = cached.text_renderer,
|
|
};
|
|
std.log.debug(
|
|
"resizing staging texture from {}x{} to {}x{}",
|
|
.{ cached.size.x, cached.size.y, size.x, size.y },
|
|
);
|
|
cached.text_renderer.deinit();
|
|
_ = cached.texture.IUnknown.Release();
|
|
self.cached = null;
|
|
}
|
|
|
|
var texture: *win32.ID3D11Texture2D = undefined;
|
|
const desc: win32.D3D11_TEXTURE2D_DESC = .{
|
|
.Width = size.x,
|
|
.Height = size.y,
|
|
.MipLevels = 1,
|
|
.ArraySize = 1,
|
|
.Format = .A8_UNORM,
|
|
.SampleDesc = .{ .Count = 1, .Quality = 0 },
|
|
.Usage = .DEFAULT,
|
|
.BindFlags = .{ .RENDER_TARGET = 1 },
|
|
.CPUAccessFlags = .{},
|
|
.MiscFlags = .{},
|
|
};
|
|
{
|
|
const hr = global.d3d.device.CreateTexture2D(&desc, null, &texture);
|
|
if (hr < 0) fatalHr("CreateStagingTexture", hr);
|
|
}
|
|
errdefer _ = texture.IUnknown.Release();
|
|
|
|
const text_renderer = TextRenderer.init(global.d2d_factory, texture);
|
|
errdefer text_renderer.deinit();
|
|
|
|
self.cached = .{
|
|
.size = size,
|
|
.texture = texture,
|
|
.text_renderer = text_renderer,
|
|
};
|
|
return .{
|
|
.texture = self.cached.?.texture,
|
|
.text_renderer = text_renderer,
|
|
};
|
|
}
|
|
};
|
|
|
|
fn createRenderTargetView(
|
|
device: *win32.ID3D11Device,
|
|
swap_chain: *win32.IDXGISwapChain,
|
|
size: XY(u32),
|
|
) *win32.ID3D11RenderTargetView {
|
|
var back_buffer: *win32.ID3D11Texture2D = undefined;
|
|
|
|
{
|
|
const hr = swap_chain.GetBuffer(0, win32.IID_ID3D11Texture2D, @ptrCast(&back_buffer));
|
|
if (hr < 0) fatalHr("SwapChainGetBuffer", hr);
|
|
}
|
|
defer _ = back_buffer.IUnknown.Release();
|
|
|
|
var target_view: *win32.ID3D11RenderTargetView = undefined;
|
|
{
|
|
const hr = device.CreateRenderTargetView(&back_buffer.ID3D11Resource, null, &target_view);
|
|
if (hr < 0) fatalHr("CreateRenderTargetView", hr);
|
|
}
|
|
|
|
{
|
|
var viewport = win32.D3D11_VIEWPORT{
|
|
.TopLeftX = 0,
|
|
.TopLeftY = 0,
|
|
.Width = @floatFromInt(size.x),
|
|
.Height = @floatFromInt(size.y),
|
|
.MinDepth = 0.0,
|
|
.MaxDepth = 0.0,
|
|
};
|
|
global.d3d.context.RSSetViewports(1, @ptrCast(&viewport));
|
|
}
|
|
// TODO: is this the right place to put this?
|
|
global.d3d.context.IASetPrimitiveTopology(._PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
|
|
|
|
return target_view;
|
|
}
|
|
|
|
fn createCellBuffer(device: *win32.ID3D11Device, count: u32) *win32.ID3D11Buffer {
|
|
var cell_buffer: *win32.ID3D11Buffer = undefined;
|
|
const buffer_desc: win32.D3D11_BUFFER_DESC = .{
|
|
.ByteWidth = count * @sizeOf(shader.Cell),
|
|
.Usage = .DYNAMIC,
|
|
.BindFlags = .{ .SHADER_RESOURCE = 1 },
|
|
.CPUAccessFlags = .{ .WRITE = 1 },
|
|
.MiscFlags = .{ .BUFFER_STRUCTURED = 1 },
|
|
.StructureByteStride = @sizeOf(shader.Cell),
|
|
};
|
|
const hr = device.CreateBuffer(&buffer_desc, null, &cell_buffer);
|
|
if (hr < 0) fatalHr("CreateCellBuffer", hr);
|
|
return cell_buffer;
|
|
}
|
|
|
|
fn getD3d11TextureMaxCellCount(cell_size: XY(u16)) XY(u16) {
|
|
comptime std.debug.assert(win32.D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION == 16384);
|
|
return .{
|
|
.x = @intCast(@divTrunc(win32.D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION, cell_size.x)),
|
|
.y = @intCast(@divTrunc(win32.D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION, cell_size.y)),
|
|
};
|
|
}
|
|
|
|
fn cellPosFromIndex(index: u32, column_count: u16) XY(u16) {
|
|
return .{
|
|
.x = @intCast(index % column_count),
|
|
.y = @intCast(@divTrunc(index, column_count)),
|
|
};
|
|
}
|
|
fn coordFromCellPos(cell_size: XY(u16), cell_pos: XY(u16)) XY(u16) {
|
|
return .{
|
|
.x = cell_size.x * cell_pos.x,
|
|
.y = cell_size.y * cell_pos.y,
|
|
};
|
|
}
|
|
|
|
fn getClientSize(comptime T: type, hwnd: win32.HWND) XY(T) {
|
|
var rect: win32.RECT = undefined;
|
|
if (0 == win32.GetClientRect(hwnd, &rect))
|
|
fatalWin32("GetClientRect", win32.GetLastError());
|
|
std.debug.assert(rect.left == 0);
|
|
std.debug.assert(rect.top == 0);
|
|
return .{ .x = @intCast(rect.right), .y = @intCast(rect.bottom) };
|
|
}
|
|
|
|
fn fatalWin32(what: []const u8, err: win32.WIN32_ERROR) noreturn {
|
|
std.debug.panic("{s} failed with {}", .{ what, err.fmt() });
|
|
}
|
|
fn fatalHr(what: []const u8, hresult: win32.HRESULT) noreturn {
|
|
std.debug.panic("{s} failed, hresult=0x{x}", .{ what, @as(u32, @bitCast(hresult)) });
|
|
}
|
|
fn oom(e: error{OutOfMemory}) noreturn {
|
|
@panic(@errorName(e));
|
|
}
|