Created
January 11, 2025 02:37
-
-
Save marler8997/b468f44b654858cc17269ea2be81c524 to your computer and use it in GitHub Desktop.
Zig Refterm Colored Cells Example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
const builtin = @import("builtin"); | |
pub fn build(b: *std.Build) void { | |
const target = b.standardTargetOptions(.{}); | |
const win32_dep = b.dependency("win32", .{}); | |
const win32_mod = win32_dep.module("zigwin32"); | |
const exe = b.addExecutable(.{ | |
.name = "example", | |
.root_source_file = b.path("example.zig"), | |
.target = target, | |
.win32_manifest = b.path("example.manifest"), | |
}); | |
exe.root_module.addImport("win32", win32_mod); | |
const install = b.addInstallArtifact(exe, .{}); | |
const run = b.addRunArtifact(exe); | |
run.step.dependOn(&install.step); | |
b.step("run", "").dependOn(&run.step); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.{ | |
.name = "example", | |
.version = "0.0.0", | |
.minimum_zig_version = "0.13.0", | |
.dependencies = .{ | |
.win32 = .{ | |
.url = "git+https://github.com/marlersoft/zigwin32#73de834b8c062849320148bfb3d872bf55146c01", | |
.hash = "122093b6b38b11c27a48c4822511aa41b4fff36719f2cb3bb7fe0f8aec2f7b89fa52", | |
}, | |
}, | |
.paths = .{ | |
".", | |
}, | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
cbuffer GridConfig : register(b0) | |
{ | |
uint2 cell_size; | |
uint2 viewport_cell_size; | |
} | |
struct Cell | |
{ | |
uint glyph_index; | |
uint background; | |
uint foreground; | |
// todo: underline flags, single/double/curly/dotted/dashed | |
// todo: underline color | |
}; | |
StructuredBuffer<Cell> cells : register(t0); | |
Texture2D<float4> glyph_texture : register(t1); | |
float4 VertexMain(uint id : SV_VERTEXID) : SV_POSITION | |
{ | |
return float4( | |
2.0 * (float(id & 1) - 0.5), | |
-(float(id >> 1) - 0.5) * 2.0, | |
0, 1 | |
); | |
} | |
float4 UnpackRgba(uint packed) | |
{ | |
float4 unpacked; | |
unpacked.r = (float)((packed >> 24) & 0xFF) / 255.0f; | |
unpacked.g = (float)((packed >> 16) & 0xFF) / 255.0f; | |
unpacked.b = (float)((packed >> 8) & 0xFF) / 255.0f; | |
unpacked.a = (float)(packed & 0xFF) / 255.0f; | |
return unpacked; | |
} | |
float4 PixelMain(float4 sv_pos : SV_POSITION) : SV_TARGET { | |
uint2 grid_pos = sv_pos.xy / cell_size; | |
uint index = grid_pos.y * viewport_cell_size.x + grid_pos.x; | |
bool show_checkerboard = false; | |
if (show_checkerboard) { | |
uint cell_count = viewport_cell_size.x * viewport_cell_size.y; | |
float strength = float(index) / float(cell_count); | |
uint checker = (grid_pos.x + grid_pos.y) % 2; | |
if (checker == 0) { | |
float shade = 1.0 - strength; | |
return float4(shade,shade,shade,1); | |
} | |
return float4(0,0,0,1); | |
} | |
// uncomment to just display the cache texture | |
// return glyph_texture[sv_pos.xy].rgba; | |
return UnpackRgba(cells[index].background); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> | |
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3"> | |
<asmv3:application> | |
<asmv3:windowsSettings> | |
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> | |
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> | |
</asmv3:windowsSettings> | |
</asmv3:application> | |
<dependency> | |
<dependentAssembly> | |
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" /> | |
</dependentAssembly> | |
</dependency> | |
</assembly> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
const win32 = @import("win32").everything; | |
const window_style_ex: win32.WINDOW_EX_STYLE = .{ | |
.APPWINDOW = 1, | |
// the redirection bitmap is unnecessary for a d3d window and causes | |
// bad artifacts when the window is resized | |
.NOREDIRECTIONBITMAP = 1, | |
}; | |
const window_style = win32.WS_OVERLAPPEDWINDOW; | |
const GridConfig = extern struct { | |
cell_size: [2]u32, | |
viewport_cell_size: [2]u32, | |
}; | |
const Cell = extern struct { | |
glyph_index: u32, | |
background: u32, | |
foreground: u32, | |
}; | |
const global = struct { | |
var d3d: D3d = undefined; | |
var shaders: Shaders = undefined; | |
var const_buf: *win32.ID3D11Buffer = undefined; | |
var state: ?State = null; | |
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
// TODO: remove | |
var rand: std.Random.DefaultPrng = undefined; | |
}; | |
// TODO: DXGI_SWAP_CHAIN_FLAG needs to be marked as a flags enum | |
//const swap_chain_flags: u32 = 0; | |
const swap_chain_flags: u32 = @intFromEnum(win32.DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT); | |
const D3d = struct { | |
device: *win32.ID3D11Device, | |
context: *win32.ID3D11DeviceContext, | |
context1: *win32.ID3D11DeviceContext1, | |
}; | |
const State = struct { | |
hwnd: win32.HWND, | |
swap_chain: *win32.IDXGISwapChain2, | |
maybe_target_view: ?*win32.ID3D11RenderTargetView = null, | |
shader_cells: ShaderCells, | |
glyph_texture: GlyphTexture, | |
}; | |
fn stateFromHwnd(hwnd: win32.HWND) *State { | |
std.debug.assert(global.state.?.hwnd == hwnd); | |
return &(global.state.?); | |
} | |
pub fn main() anyerror!void { | |
global.rand = std.Random.DefaultPrng.init(win32.GetTickCount()); | |
const CLASS_NAME = win32.L("D3dExample"); | |
const wc = win32.WNDCLASSEXW{ | |
.cbSize = @sizeOf(win32.WNDCLASSEXW), | |
.style = .{ .VREDRAW = 1, .HREDRAW = 1 }, | |
.lpfnWndProc = WndProc, | |
.cbClsExtra = 0, | |
.cbWndExtra = 0, | |
.hInstance = win32.GetModuleHandleW(null), | |
.hIcon = null, | |
.hCursor = win32.LoadCursorW(null, win32.IDC_ARROW), | |
.hbrBackground = null, | |
.lpszMenuName = null, | |
.lpszClassName = CLASS_NAME, | |
.hIconSm = null, | |
}; | |
if (0 == win32.RegisterClassExW(&wc)) fatalWin32( | |
"RegisterClass for main window", | |
win32.GetLastError(), | |
); | |
const debug_d3d = true; | |
global.d3d = initD3d(.{ .debug = debug_d3d }); | |
if (debug_d3d) { | |
const info = 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(); | |
{ | |
const buffer_desc: win32.D3D11_BUFFER_DESC = .{ | |
// d3d requires constants be sized in multiples of 16 | |
.ByteWidth = std.mem.alignForward(u32, @sizeOf(GridConfig), 16), | |
.Usage = .DYNAMIC, | |
.BindFlags = .{ .CONSTANT_BUFFER = 1 }, | |
.CPUAccessFlags = .{ .WRITE = 1 }, | |
.MiscFlags = .{}, | |
.StructureByteStride = 0, | |
}; | |
const hr = global.d3d.device.CreateBuffer(&buffer_desc, null, &global.const_buf); | |
if (hr < 0) fatalHr("CreateBuffer for grid config", hr); | |
} | |
const hwnd = win32.CreateWindowExW( | |
window_style_ex, | |
CLASS_NAME, // Window class | |
win32.L("D3d Example"), | |
window_style, | |
100, | |
100, | |
//800, | |
920, | |
600, | |
null, // Parent window | |
null, // Menu | |
win32.GetModuleHandleW(null), | |
null, | |
) orelse fatalWin32("CreateWindow", win32.GetLastError()); | |
if (0 == win32.UpdateWindow(hwnd)) fatalWin32("UpdateWindow", win32.GetLastError()); | |
_ = win32.ShowWindow(hwnd, win32.SW_SHOWNORMAL); | |
var msg: win32.MSG = undefined; | |
while (win32.GetMessageW(&msg, null, 0, 0) != 0) { | |
_ = win32.TranslateMessage(&msg); | |
_ = win32.DispatchMessageW(&msg); | |
} | |
} | |
fn initD3d(opt: struct { debug: bool }) D3d { | |
const levels = [_]win32.D3D_FEATURE_LEVEL{ | |
.@"11_0", | |
}; | |
var last_hr: i32 = undefined; | |
for (&[_]win32.D3D_DRIVER_TYPE{ .HARDWARE, .WARP }) |driver| { | |
var device: *win32.ID3D11Device = undefined; | |
var context: *win32.ID3D11DeviceContext = undefined; | |
last_hr = win32.D3D11CreateDevice( | |
null, | |
driver, | |
null, | |
.{ | |
.BGRA_SUPPORT = 1, | |
.SINGLETHREADED = 1, | |
.DEBUG = if (opt.debug) 1 else 0, | |
}, | |
&levels, | |
levels.len, | |
win32.D3D11_SDK_VERSION, | |
&device, | |
null, | |
&context, | |
); | |
if (last_hr >= 0) return .{ | |
.device = device, | |
.context = context, | |
.context1 = queryInterface(context, win32.ID3D11DeviceContext1), | |
}; | |
std.log.info( | |
"D3D11 {s} Driver error, hresult=0x{x}", | |
.{ @tagName(driver), @as(u32, @bitCast(last_hr)) }, | |
); | |
} | |
std.debug.panic("failed to initialize Direct3D11, hresult=0x{x}", .{last_hr}); | |
} | |
fn getDxgiFactory(device: *win32.ID3D11Device) *win32.IDXGIFactory2 { | |
const dxgi_device = 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, | |
// TODO: we might want to call SetBackgroundColor afterwards as that's what will be | |
// rendered outside the swap chain buffer | |
.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(); | |
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; | |
} | |
// TODO: update zigwin32 with a way to get the corresponding IID for any COM interface | |
fn queryInterface(obj: anytype, comptime Interface: type) *Interface { | |
const obj_basename_start: usize = comptime if (std.mem.lastIndexOfScalar(u8, @typeName(@TypeOf(obj)), '.')) |i| (i + 1) else 0; | |
const obj_basename = @typeName(@TypeOf(obj))[obj_basename_start..]; | |
const iface_basename_start: usize = comptime if (std.mem.lastIndexOfScalar(u8, @typeName(Interface), '.')) |i| (i + 1) else 0; | |
const iface_basename = @typeName(Interface)[iface_basename_start..]; | |
const iid_name = "IID_" ++ iface_basename; | |
const iid = @field(win32, iid_name); | |
var iface: *Interface = undefined; | |
const hr = obj.IUnknown.QueryInterface(iid, @ptrCast(&iface)); | |
if (hr < 0) std.debug.panic( | |
"QueryInferface on " ++ obj_basename ++ " as " ++ iface_basename ++ " failed, hresult={}", | |
.{@as(u32, @bitCast(hr))}, | |
); | |
return iface; | |
} | |
const Shaders = struct { | |
vertex: *win32.ID3D11VertexShader, | |
pixel: *win32.ID3D11PixelShader, | |
pub fn init() Shaders { | |
const shader_source = @embedFile("example.hlsl"); | |
var vs_blob: *win32.ID3DBlob = undefined; | |
var error_blob: ?*win32.ID3DBlob = null; | |
{ | |
const hr = win32.D3DCompile( | |
shader_source.ptr, | |
shader_source.len, | |
null, | |
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, | |
null, | |
null, | |
null, | |
"PixelMain", | |
"ps_5_0", | |
0, | |
0, | |
@ptrCast(&ps_blob), | |
@ptrCast(&error_blob), | |
); | |
if (hr < 0) { | |
reportShaderError(.pixel, error_blob); | |
fatalHr("D3DCopmilePixelShader", 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, | |
}; | |
} | |
pub fn deinit(self: *Shaders) void { | |
_ = self.pixel.IUnknown.Release(); | |
_ = self.vertex.IUnknown.Release(); | |
self.* = undefined; | |
} | |
}; | |
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()]; | |
std.log.err("{s} shader error:\n{s}\n", .{ @tagName(kind), str }); | |
} | |
fn WndProc( | |
hwnd: win32.HWND, | |
msg: u32, | |
wparam: win32.WPARAM, | |
lparam: win32.LPARAM, | |
) callconv(std.os.windows.WINAPI) win32.LRESULT { | |
switch (msg) { | |
win32.WM_CREATE => { | |
std.debug.assert(global.state == null); | |
const swap_chain = initSwapChain(global.d3d.device, hwnd); | |
global.state = .{ | |
.hwnd = hwnd, | |
.swap_chain = swap_chain, | |
.shader_cells = .{}, | |
.glyph_texture = .{ | |
.obj = undefined, // TODO | |
.view = undefined, // TODO | |
}, | |
}; | |
std.debug.assert(&(global.state.?) == stateFromHwnd(hwnd)); | |
return 0; | |
}, | |
win32.WM_DESTROY => @panic("todo"), | |
win32.WM_CLOSE => { | |
_ = win32.PostQuitMessage(0); | |
return 0; | |
}, | |
win32.WM_DPICHANGED => { | |
win32.invalidateHwnd(hwnd); | |
const rect: *win32.RECT = @ptrFromInt(@as(usize, @bitCast(lparam))); | |
if (0 == win32.SetWindowPos( | |
hwnd, | |
null, // ignored via NOZORDER | |
rect.left, | |
rect.top, | |
rect.right - rect.left, | |
rect.bottom - rect.top, | |
.{ .NOZORDER = 1 }, | |
)) fatalWin32("SetWindowPos", win32.GetLastError()); | |
return 0; | |
}, | |
win32.WM_PAINT => { | |
var ps: win32.PAINTSTRUCT = undefined; | |
_ = win32.BeginPaint(hwnd, &ps) orelse fatalWin32("BeginPaint", win32.GetLastError()); | |
defer if (0 == win32.EndPaint(hwnd, &ps)) fatalWin32("EndPaint", win32.GetLastError()); | |
const dpi = win32.dpiFromHwnd(hwnd); | |
const state = stateFromHwnd(hwnd); | |
const client_size = getClientSize(u32, hwnd); | |
{ | |
const swap_chain_size = getSwapChainSize(state.swap_chain); | |
if (swap_chain_size.x != client_size.x or swap_chain_size.y != client_size.y) { | |
if (false) std.log.info( | |
"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, | |
@intCast(client_size.x), | |
@intCast(client_size.y), | |
.UNKNOWN, | |
swap_chain_flags, | |
); | |
if (hr < 0) fatalHr("ResizeBuffers", hr); | |
} | |
} | |
} | |
const cell_size: XY(u32) = .{ | |
.x = win32.scaleDpi(u32, 11, dpi), | |
.y = win32.scaleDpi(u32, 18, dpi), | |
}; | |
const viewport_cell_size: XY(u32) = .{ | |
.x = @divTrunc(client_size.x + cell_size.x - 1, cell_size.x), | |
.y = @divTrunc(client_size.y + cell_size.y - 1, 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: *GridConfig = @ptrCast(@alignCast(mapped.pData)); | |
config.cell_size[0] = @intCast(cell_size.x); | |
config.cell_size[1] = @intCast(cell_size.y); | |
config.viewport_cell_size[0] = @intCast(viewport_cell_size.x); | |
config.viewport_cell_size[1] = @intCast(viewport_cell_size.y); | |
} | |
{ | |
const cell_count = viewport_cell_size.x * viewport_cell_size.y; | |
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: [*]Cell = @ptrCast(@alignCast(mapped.pData)); | |
for (0..state.shader_cells.count) |i| { | |
cells[i] = .{ | |
.glyph_index = global.rand.random().int(u32) % 256, | |
.background = global.rand.random().int(u32) | 0xff, | |
.foreground = global.rand.random().int(u32) | 0xff, | |
}; | |
} | |
} | |
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, 1, &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); | |
} | |
return 0; | |
}, | |
else => return win32.DefWindowProcW(hwnd, msg, wparam, lparam), | |
} | |
} | |
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; | |
if (false) std.log.info("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 { | |
obj: *win32.ID3D11Texture2D, | |
view: *win32.ID3D11ShaderResourceView, | |
}; | |
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(Cell), | |
.Usage = .DYNAMIC, | |
.BindFlags = .{ .SHADER_RESOURCE = 1 }, | |
.CPUAccessFlags = .{ .WRITE = 1 }, | |
.MiscFlags = .{ .BUFFER_STRUCTURED = 1 }, | |
.StructureByteStride = @sizeOf(Cell), | |
}; | |
const hr = device.CreateBuffer(&buffer_desc, null, &cell_buffer); | |
if (hr < 0) fatalHr("CreateCellBuffer", hr); | |
return cell_buffer; | |
} | |
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) }; | |
} | |
pub fn XY(comptime T: type) type { | |
return struct { | |
x: T, | |
y: T, | |
pub fn init(x: T, y: T) @This() { | |
return .{ .x = x, .y = y }; | |
} | |
const Self = @This(); | |
pub fn eql(self: Self, other: Self) bool { | |
return self.x == other.x and self.y == other.y; | |
} | |
}; | |
} | |
threadlocal var thread_is_panicing = false; | |
pub fn panic( | |
msg: []const u8, | |
error_return_trace: ?*std.builtin.StackTrace, | |
ret_addr: ?usize, | |
) noreturn { | |
if (!thread_is_panicing) { | |
thread_is_panicing = true; | |
const msg_z: [:0]const u8 = if (std.fmt.allocPrintZ( | |
std.heap.page_allocator, | |
"{s}", | |
.{msg}, | |
)) |msg_z| msg_z else |_| "failed allocate error message"; | |
_ = win32.MessageBoxA(null, msg_z, "Flow Panic", .{ .ICONASTERISK = 1 }); | |
} | |
std.builtin.default_panic(msg, error_return_trace, ret_addr); | |
} | |
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)) }); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment