From 7558a6381904617c2245422deec5297d9946b9ad Mon Sep 17 00:00:00 2001 From: Jonathan Marler Date: Wed, 15 Jan 2025 21:16:51 -0700 Subject: [PATCH] win32 gui: decouple screen renderer from text renderer I tried adding Andrew's TrueType renderer as an option, however, I realized it doesn't work with zig 0.13.0, so it'll have to wait. Until then here's the changes needed to decouple the screen/text rendering which would make it easier to add more text renderers and just cleans things up a bit more in general. --- src/win32/DwriteRenderer.zig | 223 +++++++++++------------------------ src/win32/d3d11.zig | 107 +++++++++++++++-- 2 files changed, 163 insertions(+), 167 deletions(-) diff --git a/src/win32/DwriteRenderer.zig b/src/win32/DwriteRenderer.zig index 03d8861..0e17630 100644 --- a/src/win32/DwriteRenderer.zig +++ b/src/win32/DwriteRenderer.zig @@ -7,145 +7,76 @@ const win32ext = @import("win32ext.zig"); const dwrite = @import("dwrite.zig"); const XY = @import("xy.zig").XY; -staging_texture: StagingTexture = .{}, +pub const Font = dwrite.Font; -const StagingTexture = struct { - const Cached = struct { - size: XY(u16), - texture: *win32.ID3D11Texture2D, - render_target: *win32.ID2D1RenderTarget, - white_brush: *win32.ID2D1SolidColorBrush, - }; - cached: ?Cached = null, - pub fn update( - self: *StagingTexture, - d3d_device: *win32.ID3D11Device, - d2d_factory: *win32.ID2D1Factory, - size: XY(u16), - ) struct { - texture: *win32.ID3D11Texture2D, - render_target: *win32.ID2D1RenderTarget, - white_brush: *win32.ID2D1SolidColorBrush, - } { - if (self.cached) |cached| { - if (cached.size.eql(size)) return .{ - .texture = cached.texture, - .render_target = cached.render_target, - .white_brush = cached.white_brush, - }; - std.log.debug( - "resizing staging texture from {}x{} to {}x{}", - .{ cached.size.x, cached.size.y, size.x, size.y }, - ); - _ = cached.white_brush.IUnknown.Release(); - _ = cached.render_target.IUnknown.Release(); - _ = cached.texture.IUnknown.Release(); - self.cached = null; - } +pub const needs_direct2d = true; - 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 = d3d_device.CreateTexture2D(&desc, null, &texture); - if (hr < 0) fatalHr("CreateStagingTexture", hr); - } - errdefer _ = texture.IUnknown.Release(); - - const dxgi_surface = win32ext.queryInterface(texture, win32.IDXGISurface); - defer _ = dxgi_surface.IUnknown.Release(); - - var render_target: *win32.ID2D1RenderTarget = undefined; - { - const props = win32.D2D1_RENDER_TARGET_PROPERTIES{ - .type = .DEFAULT, - .pixelFormat = .{ - .format = .A8_UNORM, - .alphaMode = .PREMULTIPLIED, - }, - .dpiX = 0, - .dpiY = 0, - .usage = .{}, - .minLevel = .DEFAULT, - }; - const hr = d2d_factory.CreateDxgiSurfaceRenderTarget( - dxgi_surface, - &props, - &render_target, - ); - if (hr < 0) fatalHr("CreateDxgiSurfaceRenderTarget", hr); - } - errdefer _ = render_target.IUnknown.Release(); - - { - const dc = win32ext.queryInterface(render_target, win32.ID2D1DeviceContext); - defer _ = dc.IUnknown.Release(); - dc.SetUnitMode(win32.D2D1_UNIT_MODE_PIXELS); - } - - var white_brush: *win32.ID2D1SolidColorBrush = undefined; - { - const hr = render_target.CreateSolidColorBrush( - &.{ .r = 1.0, .g = 1.0, .b = 1.0, .a = 1.0 }, - null, - &white_brush, - ); - if (hr < 0) fatalHr("CreateSolidColorBrush", hr); - } - errdefer _ = white_brush.IUnknown.Release(); - - self.cached = .{ - .size = size, - .texture = texture, - .render_target = render_target, - .white_brush = white_brush, - }; - return .{ - .texture = self.cached.?.texture, - .render_target = self.cached.?.render_target, - .white_brush = self.cached.?.white_brush, +render_target: *win32.ID2D1RenderTarget, +white_brush: *win32.ID2D1SolidColorBrush, +pub fn init( + d2d_factory: *win32.ID2D1Factory, + texture: *win32.ID3D11Texture2D, +) DwriteRenderer { + const dxgi_surface = win32ext.queryInterface(texture, win32.IDXGISurface); + defer _ = dxgi_surface.IUnknown.Release(); + + var render_target: *win32.ID2D1RenderTarget = undefined; + { + const props = win32.D2D1_RENDER_TARGET_PROPERTIES{ + .type = .DEFAULT, + .pixelFormat = .{ + .format = .A8_UNORM, + .alphaMode = .PREMULTIPLIED, + }, + .dpiX = 0, + .dpiY = 0, + .usage = .{}, + .minLevel = .DEFAULT, }; + const hr = d2d_factory.CreateDxgiSurfaceRenderTarget( + dxgi_surface, + &props, + &render_target, + ); + if (hr < 0) fatalHr("CreateDxgiSurfaceRenderTarget", hr); } -}; + errdefer _ = render_target.IUnknown.Release(); + + { + const dc = win32ext.queryInterface(render_target, win32.ID2D1DeviceContext); + defer _ = dc.IUnknown.Release(); + dc.SetUnitMode(win32.D2D1_UNIT_MODE_PIXELS); + } + + var white_brush: *win32.ID2D1SolidColorBrush = undefined; + { + const hr = render_target.CreateSolidColorBrush( + &.{ .r = 1.0, .g = 1.0, .b = 1.0, .a = 1.0 }, + null, + &white_brush, + ); + if (hr < 0) fatalHr("CreateSolidColorBrush", hr); + } + errdefer _ = white_brush.IUnknown.Release(); + + return .{ + .render_target = render_target, + .white_brush = white_brush, + }; +} +pub fn deinit(self: *DwriteRenderer) void { + _ = self.white_brush.IUnknown.Release(); + _ = self.render_target.IUnknown.Release(); + self.* = undefined; +} pub fn render( - self: *DwriteRenderer, - d3d_device: *win32.ID3D11Device, - d3d_context: *win32.ID3D11DeviceContext, - d2d_factory: *win32.ID2D1Factory, - font: dwrite.Font, - texture: *win32.ID3D11Texture2D, - codepoint: u21, - coord: XY(u16), + self: *const DwriteRenderer, + font: Font, + utf8: []const u8, ) void { - const staging = self.staging_texture.update( - d3d_device, - d2d_factory, - font.cell_size, - ); - var utf16_buf: [10]u16 = undefined; - - const utf16_len = blk: { - 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) }, - ); - const utf8 = utf8_buf[0..utf8_len]; - break :blk std.unicode.utf8ToUtf16Le(&utf16_buf, utf8) catch unreachable; - }; - + const utf16_len = std.unicode.utf8ToUtf16Le(&utf16_buf, utf8) catch unreachable; const utf16 = utf16_buf[0..utf16_len]; std.debug.assert(utf16.len <= 2); @@ -156,48 +87,28 @@ pub fn render( .right = @floatFromInt(font.cell_size.x), .bottom = @floatFromInt(font.cell_size.y), }; - staging.render_target.BeginDraw(); + self.render_target.BeginDraw(); { const color: win32.D2D_COLOR_F = .{ .r = 0, .g = 0, .b = 0, .a = 0 }; - staging.render_target.Clear(&color); + self.render_target.Clear(&color); } - staging.render_target.DrawText( + self.render_target.DrawText( @ptrCast(utf16.ptr), @intCast(utf16.len), font.text_format, &rect, - &staging.white_brush.ID2D1Brush, + &self.white_brush.ID2D1Brush, .{}, .NATURAL, ); var tag1: u64 = undefined; var tag2: u64 = undefined; - const hr = staging.render_target.EndDraw(&tag1, &tag2); + const hr = self.render_target.EndDraw(&tag1, &tag2); if (hr < 0) std.debug.panic( "D2D DrawText failed, hresult=0x{x}, tag1={}, tag2={}", .{ @as(u32, @bitCast(hr)), tag1, tag2 }, ); } - - const box: win32.D3D11_BOX = .{ - .left = 0, - .top = 0, - .front = 0, - .right = font.cell_size.x, - .bottom = font.cell_size.y, - .back = 1, - }; - - d3d_context.CopySubresourceRegion( - &texture.ID3D11Resource, - 0, // subresource - coord.x, - coord.y, - 0, // z - &staging.texture.ID3D11Resource, - 0, // subresource - &box, - ); } fn fatalHr(what: []const u8, hresult: win32.HRESULT) noreturn { diff --git a/src/win32/d3d11.zig b/src/win32/d3d11.zig index 07bbbed..25392d2 100644 --- a/src/win32/d3d11.zig +++ b/src/win32/d3d11.zig @@ -6,9 +6,10 @@ 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 = dwrite.Font; +pub const Font = TextRenderer.Font; const log = std.log.scoped(.d3d); @@ -16,14 +17,16 @@ const log = std.log.scoped(.d3d); // 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: *win32.ID2D1Factory = undefined; + var d2d_factory: D2dFactory = undefined; var glyph_cache_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - var text_renderer: TextRenderer = undefined; + var staging_texture: StagingTexture = .{}; var background: Rgba8 = .{ .r = 19, .g = 19, .b = 19, .a = 255 }; }; @@ -106,7 +109,7 @@ pub fn init(opt: struct { if (hr < 0) fatalHr("CreateBuffer for grid config", hr); } - { + if (TextRenderer.needs_direct2d) { const hr = win32.D2D1CreateFactory( .SINGLE_THREADED, win32.IID_ID2D1Factory, @@ -189,14 +192,34 @@ pub const WindowState = struct { // 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); - global.text_renderer.render( - global.d3d.device, - global.d3d.context, - global.d2d_factory, + const staging = global.staging_texture.update(font.cell_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, - state.glyph_texture.obj, - codepoint, - coord, + utf8_buf[0..utf8_len], + ); + const box: win32.D3D11_BOX = .{ + .left = 0, + .top = 0, + .front = 0, + .right = 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; }, @@ -683,6 +706,68 @@ const GlyphTexture = struct { } }; +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,