win32 gui: replace D2D renderer with D3d11 shader
This commit is contained in:
parent
157dc3d47c
commit
5b83619f7a
13 changed files with 1945 additions and 425 deletions
|
@ -161,6 +161,13 @@ pub fn build_exe(
|
||||||
options.addOption(bool, "use_tree_sitter", use_tree_sitter);
|
options.addOption(bool, "use_tree_sitter", use_tree_sitter);
|
||||||
options.addOption(bool, "strip", strip);
|
options.addOption(bool, "strip", strip);
|
||||||
options.addOption(bool, "gui", gui);
|
options.addOption(bool, "gui", gui);
|
||||||
|
if (gui) {
|
||||||
|
options.addOption(bool, "d2d", b.option(
|
||||||
|
bool,
|
||||||
|
"d2d",
|
||||||
|
"use the Direct2D backend (instead of Direct3D11)",
|
||||||
|
) orelse false);
|
||||||
|
}
|
||||||
|
|
||||||
const options_mod = options.createModule();
|
const options_mod = options.createModule();
|
||||||
|
|
||||||
|
@ -323,6 +330,7 @@ pub fn build_exe(
|
||||||
const gui_mod = b.createModule(.{
|
const gui_mod = b.createModule(.{
|
||||||
.root_source_file = b.path("src/win32/gui.zig"),
|
.root_source_file = b.path("src/win32/gui.zig"),
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
|
.{ .name = "build_options", .module = options_mod },
|
||||||
.{ .name = "win32", .module = win32_mod },
|
.{ .name = "win32", .module = win32_mod },
|
||||||
.{ .name = "ddui", .module = direct2d_dep.module("ddui") },
|
.{ .name = "ddui", .module = direct2d_dep.module("ddui") },
|
||||||
.{ .name = "cbor", .module = cbor_mod },
|
.{ .name = "cbor", .module = cbor_mod },
|
||||||
|
|
|
@ -126,8 +126,8 @@ pub fn fmtmsg(buf: []u8, value: anytype) []const u8 {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(self: *Self) error{}!void {
|
pub fn render(self: *Self) error{}!void {
|
||||||
_ = gui.updateScreen(&self.vx.screen);
|
const hwnd = self.hwnd orelse return;
|
||||||
if (self.hwnd) |hwnd| win32.invalidateHwnd(hwnd);
|
_ = gui.updateScreen(hwnd, &self.vx.screen);
|
||||||
}
|
}
|
||||||
pub fn stop(self: *Self) void {
|
pub fn stop(self: *Self) void {
|
||||||
// this is guaranteed because stop won't be called until after
|
// this is guaranteed because stop won't be called until after
|
||||||
|
|
205
src/win32/DwriteRenderer.zig
Normal file
205
src/win32/DwriteRenderer.zig
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
const DwriteRenderer = @This();
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const win32 = @import("win32").everything;
|
||||||
|
const win32ext = @import("win32ext.zig");
|
||||||
|
|
||||||
|
const dwrite = @import("dwrite.zig");
|
||||||
|
const XY = @import("xy.zig").XY;
|
||||||
|
|
||||||
|
staging_texture: StagingTexture = .{},
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
var texture: *win32.ID3D11Texture2D = undefined;
|
||||||
|
const desc: win32.D3D11_TEXTURE2D_DESC = .{
|
||||||
|
.Width = size.x,
|
||||||
|
.Height = size.y,
|
||||||
|
.MipLevels = 1,
|
||||||
|
.ArraySize = 1,
|
||||||
|
.Format = .B8G8R8A8_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 = .B8G8R8A8_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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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),
|
||||||
|
) 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 = utf16_buf[0..utf16_len];
|
||||||
|
std.debug.assert(utf16.len <= 2);
|
||||||
|
|
||||||
|
{
|
||||||
|
const rect: win32.D2D_RECT_F = .{
|
||||||
|
.left = 0,
|
||||||
|
.top = 0,
|
||||||
|
.right = @floatFromInt(font.cell_size.x),
|
||||||
|
.bottom = @floatFromInt(font.cell_size.y),
|
||||||
|
};
|
||||||
|
staging.render_target.BeginDraw();
|
||||||
|
{
|
||||||
|
const color: win32.D2D_COLOR_F = .{ .r = 0, .g = 0, .b = 0, .a = 0 };
|
||||||
|
staging.render_target.Clear(&color);
|
||||||
|
}
|
||||||
|
staging.render_target.DrawText(
|
||||||
|
@ptrCast(utf16.ptr),
|
||||||
|
@intCast(utf16.len),
|
||||||
|
font.text_format,
|
||||||
|
&rect,
|
||||||
|
&staging.white_brush.ID2D1Brush,
|
||||||
|
.{},
|
||||||
|
.NATURAL,
|
||||||
|
);
|
||||||
|
var tag1: u64 = undefined;
|
||||||
|
var tag2: u64 = undefined;
|
||||||
|
const hr = staging.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 {
|
||||||
|
std.debug.panic("{s} failed, hresult=0x{x}", .{ what, @as(u32, @bitCast(hresult)) });
|
||||||
|
}
|
31
src/win32/FontFace.zig
Normal file
31
src/win32/FontFace.zig
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
const FontFace = @This();
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
// it seems that Windows only supports font faces with up to 31 characters
|
||||||
|
pub const max = 31;
|
||||||
|
|
||||||
|
buf: [max + 1]u16,
|
||||||
|
len: u5,
|
||||||
|
|
||||||
|
pub fn initUtf8(utf8: []const u8) error{ TooLong, InvalidUtf8 }!FontFace {
|
||||||
|
const utf16_len = std.unicode.calcUtf16LeLen(utf8) catch return error.InvalidUtf8;
|
||||||
|
if (utf16_len > max)
|
||||||
|
return error.TooLong;
|
||||||
|
var result: FontFace = .{ .buf = undefined, .len = @intCast(utf16_len) };
|
||||||
|
result.buf[utf16_len] = 0;
|
||||||
|
const actual_len = try std.unicode.utf8ToUtf16Le(&result.buf, utf8);
|
||||||
|
std.debug.assert(actual_len == utf16_len);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ptr(self: *const FontFace) [*:0]const u16 {
|
||||||
|
std.debug.assert(self.buf[@as(usize, self.len)] == 0);
|
||||||
|
return @ptrCast(&self.buf);
|
||||||
|
}
|
||||||
|
pub fn slice(self: *const FontFace) [:0]const u16 {
|
||||||
|
return self.ptr()[0..self.len :0];
|
||||||
|
}
|
||||||
|
pub fn eql(self: *const FontFace, other: *const FontFace) bool {
|
||||||
|
return std.mem.eql(u16, self.slice(), other.slice());
|
||||||
|
}
|
224
src/win32/GlyphIndexCache.zig
Normal file
224
src/win32/GlyphIndexCache.zig
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
const GlyphIndexCache = @This();
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Node = struct {
|
||||||
|
prev: ?u32,
|
||||||
|
next: ?u32,
|
||||||
|
codepoint: ?u21,
|
||||||
|
};
|
||||||
|
|
||||||
|
map: std.AutoHashMapUnmanaged(u21, u32) = .{},
|
||||||
|
nodes: []Node,
|
||||||
|
front: u32,
|
||||||
|
back: u32,
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator, capacity: u32) error{OutOfMemory}!GlyphIndexCache {
|
||||||
|
var result: GlyphIndexCache = .{
|
||||||
|
.map = .{},
|
||||||
|
.nodes = try allocator.alloc(Node, capacity),
|
||||||
|
.front = undefined,
|
||||||
|
.back = undefined,
|
||||||
|
};
|
||||||
|
result.clearRetainingCapacity();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clearRetainingCapacity(self: *GlyphIndexCache) void {
|
||||||
|
self.map.clearRetainingCapacity();
|
||||||
|
self.nodes[0] = .{ .prev = null, .next = 1, .codepoint = null };
|
||||||
|
self.nodes[self.nodes.len - 1] = .{ .prev = @intCast(self.nodes.len - 2), .next = null, .codepoint = null };
|
||||||
|
for (self.nodes[1 .. self.nodes.len - 1], 1..) |*node, index| {
|
||||||
|
node.* = .{
|
||||||
|
.prev = @intCast(index - 1),
|
||||||
|
.next = @intCast(index + 1),
|
||||||
|
.codepoint = null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
self.front = 0;
|
||||||
|
self.back = @intCast(self.nodes.len - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *GlyphIndexCache, allocator: std.mem.Allocator) void {
|
||||||
|
allocator.free(self.nodes);
|
||||||
|
self.map.deinit(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isFull(self: *const GlyphIndexCache) bool {
|
||||||
|
return self.map.count() == self.nodes.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Reserved = struct {
|
||||||
|
index: u32,
|
||||||
|
replaced: ?u21,
|
||||||
|
};
|
||||||
|
pub fn reserve(self: *GlyphIndexCache, allocator: std.mem.Allocator, codepoint: u21) error{OutOfMemory}!union(enum) {
|
||||||
|
newly_reserved: Reserved,
|
||||||
|
already_reserved: u32,
|
||||||
|
} {
|
||||||
|
{
|
||||||
|
const entry = try self.map.getOrPut(allocator, codepoint);
|
||||||
|
if (entry.found_existing) {
|
||||||
|
self.moveToBack(entry.value_ptr.*);
|
||||||
|
return .{ .already_reserved = entry.value_ptr.* };
|
||||||
|
}
|
||||||
|
entry.value_ptr.* = self.front;
|
||||||
|
}
|
||||||
|
|
||||||
|
std.debug.assert(self.nodes[self.front].prev == null);
|
||||||
|
std.debug.assert(self.nodes[self.front].next != null);
|
||||||
|
const replaced = self.nodes[self.front].codepoint;
|
||||||
|
self.nodes[self.front].codepoint = codepoint;
|
||||||
|
if (replaced) |r| {
|
||||||
|
const removed = self.map.remove(r);
|
||||||
|
std.debug.assert(removed);
|
||||||
|
}
|
||||||
|
const save_front = self.front;
|
||||||
|
self.moveToBack(self.front);
|
||||||
|
return .{ .newly_reserved = .{ .index = save_front, .replaced = replaced } };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn moveToBack(self: *GlyphIndexCache, index: u32) void {
|
||||||
|
if (index == self.back) return;
|
||||||
|
|
||||||
|
const node = &self.nodes[index];
|
||||||
|
if (node.prev) |prev| {
|
||||||
|
self.nodes[prev].next = node.next;
|
||||||
|
} else {
|
||||||
|
self.front = node.next.?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.next) |next| {
|
||||||
|
self.nodes[next].prev = node.prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.nodes[self.back].next = index;
|
||||||
|
node.prev = self.back;
|
||||||
|
node.next = null;
|
||||||
|
self.back = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn testValidate(self: *const GlyphIndexCache, seen: []bool) !void {
|
||||||
|
for (seen) |*s| {
|
||||||
|
s.* = false;
|
||||||
|
}
|
||||||
|
try std.testing.expectEqual(null, self.nodes[self.front].prev);
|
||||||
|
try std.testing.expect(self.nodes[self.front].next != null);
|
||||||
|
seen[self.front] = true;
|
||||||
|
try std.testing.expectEqual(null, self.nodes[self.back].next);
|
||||||
|
try std.testing.expect(self.nodes[self.back].prev != null);
|
||||||
|
seen[self.back] = true;
|
||||||
|
|
||||||
|
var index = self.nodes[self.front].next.?;
|
||||||
|
var count: u32 = 1;
|
||||||
|
while (index != self.back) : ({
|
||||||
|
count += 1;
|
||||||
|
index = self.nodes[index].next orelse break;
|
||||||
|
}) {
|
||||||
|
try std.testing.expect(!seen[index]);
|
||||||
|
seen[index] = true;
|
||||||
|
const node = &self.nodes[index];
|
||||||
|
try std.testing.expect(node.prev != null);
|
||||||
|
try std.testing.expect(node.next != null);
|
||||||
|
try std.testing.expectEqual(index, self.nodes[node.prev.?].next.?);
|
||||||
|
try std.testing.expectEqual(index, self.nodes[node.next.?].prev.?);
|
||||||
|
}
|
||||||
|
try std.testing.expectEqual(self.nodes.len - 1, count);
|
||||||
|
for (seen) |s| {
|
||||||
|
try std.testing.expect(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "GlyphIndexCache" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const allocator = testing.allocator;
|
||||||
|
|
||||||
|
var validation_buf: [3]bool = undefined;
|
||||||
|
var cache = try GlyphIndexCache.init(allocator, 3);
|
||||||
|
defer cache.deinit(allocator);
|
||||||
|
|
||||||
|
try cache.testValidate(&validation_buf);
|
||||||
|
|
||||||
|
switch (try cache.reserve(allocator, 'A')) {
|
||||||
|
.newly_reserved => |reserved| {
|
||||||
|
try cache.testValidate(&validation_buf);
|
||||||
|
try testing.expectEqual(0, reserved.index);
|
||||||
|
try testing.expectEqual(null, reserved.replaced);
|
||||||
|
try testing.expectEqual(reserved.index, cache.back);
|
||||||
|
try testing.expect(!cache.isFull());
|
||||||
|
},
|
||||||
|
else => return error.TestUnexpectedResult,
|
||||||
|
}
|
||||||
|
switch (try cache.reserve(allocator, 'A')) {
|
||||||
|
.already_reserved => |index| {
|
||||||
|
try cache.testValidate(&validation_buf);
|
||||||
|
try testing.expectEqual(0, index);
|
||||||
|
try testing.expectEqual(index, cache.back);
|
||||||
|
try testing.expect(!cache.isFull());
|
||||||
|
},
|
||||||
|
else => return error.TestUnexpectedResult,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (try cache.reserve(allocator, 'B')) {
|
||||||
|
.newly_reserved => |reserved| {
|
||||||
|
try cache.testValidate(&validation_buf);
|
||||||
|
try testing.expectEqual(1, reserved.index);
|
||||||
|
try testing.expectEqual(null, reserved.replaced);
|
||||||
|
try testing.expectEqual(reserved.index, cache.back);
|
||||||
|
try testing.expect(!cache.isFull());
|
||||||
|
},
|
||||||
|
else => return error.TestUnexpectedResult,
|
||||||
|
}
|
||||||
|
switch (try cache.reserve(allocator, 'A')) {
|
||||||
|
.already_reserved => |index| {
|
||||||
|
try cache.testValidate(&validation_buf);
|
||||||
|
try testing.expectEqual(0, index);
|
||||||
|
try testing.expectEqual(index, cache.back);
|
||||||
|
try testing.expectEqual(2, cache.front);
|
||||||
|
try testing.expect(!cache.isFull());
|
||||||
|
},
|
||||||
|
else => return error.TestUnexpectedResult,
|
||||||
|
}
|
||||||
|
|
||||||
|
for (0..6) |run| {
|
||||||
|
const index: u32 = @intCast(run % 2);
|
||||||
|
cache.moveToBack(index);
|
||||||
|
try cache.testValidate(&validation_buf);
|
||||||
|
try testing.expectEqual(index, cache.back);
|
||||||
|
try testing.expectEqual(2, cache.front);
|
||||||
|
try testing.expect(!cache.isFull());
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (try cache.reserve(allocator, 'C')) {
|
||||||
|
.newly_reserved => |reserved| {
|
||||||
|
try cache.testValidate(&validation_buf);
|
||||||
|
try testing.expectEqual(2, reserved.index);
|
||||||
|
try testing.expectEqual(null, reserved.replaced);
|
||||||
|
try testing.expectEqual(reserved.index, cache.back);
|
||||||
|
try testing.expect(cache.isFull());
|
||||||
|
},
|
||||||
|
else => return error.TestUnexpectedResult,
|
||||||
|
}
|
||||||
|
|
||||||
|
for (0..10) |run| {
|
||||||
|
const index: u32 = @intCast(run % 3);
|
||||||
|
cache.moveToBack(index);
|
||||||
|
try cache.testValidate(&validation_buf);
|
||||||
|
try testing.expectEqual(index, cache.back);
|
||||||
|
try testing.expect(cache.isFull());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const expected_index = cache.front;
|
||||||
|
const expected_replaced = cache.nodes[cache.front].codepoint.?;
|
||||||
|
switch (try cache.reserve(allocator, 'D')) {
|
||||||
|
.newly_reserved => |reserved| {
|
||||||
|
try cache.testValidate(&validation_buf);
|
||||||
|
try testing.expectEqual(expected_index, reserved.index);
|
||||||
|
try testing.expectEqual(expected_replaced, reserved.replaced.?);
|
||||||
|
try testing.expectEqual(reserved.index, cache.back);
|
||||||
|
try testing.expect(cache.isFull());
|
||||||
|
},
|
||||||
|
else => return error.TestUnexpectedResult,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
250
src/win32/d2d.zig
Normal file
250
src/win32/d2d.zig
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const win32 = @import("win32").everything;
|
||||||
|
const win32ext = @import("win32ext.zig");
|
||||||
|
const ddui = @import("ddui");
|
||||||
|
const vaxis = @import("vaxis");
|
||||||
|
const dwrite = @import("dwrite.zig");
|
||||||
|
|
||||||
|
const RGB = @import("color").RGB;
|
||||||
|
const xterm = @import("xterm.zig");
|
||||||
|
const XY = @import("xy.zig").XY;
|
||||||
|
|
||||||
|
pub const Font = dwrite.Font;
|
||||||
|
|
||||||
|
pub const NOREDIRECTIONBITMAP = 0;
|
||||||
|
|
||||||
|
const global = struct {
|
||||||
|
var init_called: bool = false;
|
||||||
|
var d2d_factory: *win32.ID2D1Factory = undefined;
|
||||||
|
var background: win32.D2D_COLOR_F = .{ .r = 0.075, .g = 0.075, .b = 0.075, .a = 1.0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init() void {
|
||||||
|
std.debug.assert(!global.init_called);
|
||||||
|
global.init_called = true;
|
||||||
|
dwrite.init();
|
||||||
|
{
|
||||||
|
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, rgb: RGB) void {
|
||||||
|
_ = state;
|
||||||
|
global.background = ddui.rgb8(rgb.r, rgb.g, rgb.b);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const WindowState = struct {
|
||||||
|
maybe_d2d: ?D2d = null,
|
||||||
|
pub fn init(hwnd: win32.HWND) WindowState {
|
||||||
|
_ = hwnd;
|
||||||
|
return .{};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const D2d = struct {
|
||||||
|
target: *win32.ID2D1HwndRenderTarget,
|
||||||
|
brush: *win32.ID2D1SolidColorBrush,
|
||||||
|
pub fn init(hwnd: win32.HWND, err: *ddui.HResultError) error{HResult}!D2d {
|
||||||
|
var target: *win32.ID2D1HwndRenderTarget = undefined;
|
||||||
|
const target_props = win32.D2D1_RENDER_TARGET_PROPERTIES{
|
||||||
|
.type = .DEFAULT,
|
||||||
|
.pixelFormat = .{
|
||||||
|
.format = .B8G8R8A8_UNORM,
|
||||||
|
.alphaMode = .PREMULTIPLIED,
|
||||||
|
},
|
||||||
|
.dpiX = 0,
|
||||||
|
.dpiY = 0,
|
||||||
|
.usage = .{},
|
||||||
|
.minLevel = .DEFAULT,
|
||||||
|
};
|
||||||
|
const hwnd_target_props = win32.D2D1_HWND_RENDER_TARGET_PROPERTIES{
|
||||||
|
.hwnd = hwnd,
|
||||||
|
.pixelSize = .{ .width = 0, .height = 0 },
|
||||||
|
.presentOptions = .{},
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
const hr = global.d2d_factory.CreateHwndRenderTarget(
|
||||||
|
&target_props,
|
||||||
|
&hwnd_target_props,
|
||||||
|
&target,
|
||||||
|
);
|
||||||
|
if (hr < 0) return err.set(hr, "CreateHwndRenderTarget");
|
||||||
|
}
|
||||||
|
errdefer _ = target.IUnknown.Release();
|
||||||
|
|
||||||
|
{
|
||||||
|
const dc = win32ext.queryInterface(target, win32.ID2D1DeviceContext);
|
||||||
|
defer _ = dc.IUnknown.Release();
|
||||||
|
// just make everything DPI aware, all applications should just do this
|
||||||
|
dc.SetUnitMode(win32.D2D1_UNIT_MODE_PIXELS);
|
||||||
|
}
|
||||||
|
|
||||||
|
var brush: *win32.ID2D1SolidColorBrush = undefined;
|
||||||
|
{
|
||||||
|
const color: win32.D2D_COLOR_F = .{ .r = 0, .g = 0, .b = 0, .a = 0 };
|
||||||
|
const hr = target.ID2D1RenderTarget.CreateSolidColorBrush(&color, null, &brush);
|
||||||
|
if (hr < 0) return err.set(hr, "CreateSolidBrush");
|
||||||
|
}
|
||||||
|
errdefer _ = brush.IUnknown.Release();
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.target = target,
|
||||||
|
.brush = brush,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: *D2d) void {
|
||||||
|
_ = self.brush.IUnknown.Release();
|
||||||
|
_ = self.target.IUnknown.Release();
|
||||||
|
}
|
||||||
|
pub fn solid(self: *const D2d, color: win32.D2D_COLOR_F) *win32.ID2D1Brush {
|
||||||
|
self.brush.SetColor(&color);
|
||||||
|
return &self.brush.ID2D1Brush;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn paint(
|
||||||
|
hwnd: win32.HWND,
|
||||||
|
state: *WindowState,
|
||||||
|
font: Font,
|
||||||
|
screen: *const vaxis.Screen,
|
||||||
|
) void {
|
||||||
|
const client_size = getClientSize(hwnd);
|
||||||
|
|
||||||
|
const err: ddui.HResultError = blk: {
|
||||||
|
var ps: win32.PAINTSTRUCT = undefined;
|
||||||
|
_ = win32.BeginPaint(hwnd, &ps) orelse return fatalWin32(
|
||||||
|
"BeginPaint",
|
||||||
|
win32.GetLastError(),
|
||||||
|
);
|
||||||
|
defer if (0 == win32.EndPaint(hwnd, &ps)) fatalWin32(
|
||||||
|
"EndPaint",
|
||||||
|
win32.GetLastError(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (state.maybe_d2d == null) {
|
||||||
|
var err: ddui.HResultError = undefined;
|
||||||
|
state.maybe_d2d = D2d.init(hwnd, &err) catch break :blk err;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const size: win32.D2D_SIZE_U = .{
|
||||||
|
.width = @intCast(client_size.x),
|
||||||
|
.height = @intCast(client_size.y),
|
||||||
|
};
|
||||||
|
const hr = state.maybe_d2d.?.target.Resize(&size);
|
||||||
|
if (hr < 0) break :blk ddui.HResultError{ .context = "D2dResize", .hr = hr };
|
||||||
|
}
|
||||||
|
|
||||||
|
state.maybe_d2d.?.target.ID2D1RenderTarget.BeginDraw();
|
||||||
|
paintD2d(&state.maybe_d2d.?, screen, font);
|
||||||
|
break :blk ddui.HResultError{
|
||||||
|
.context = "D2dEndDraw",
|
||||||
|
.hr = state.maybe_d2d.?.target.ID2D1RenderTarget.EndDraw(null, null),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (err.hr == win32.D2DERR_RECREATE_TARGET) {
|
||||||
|
std.log.debug("D2DERR_RECREATE_TARGET", .{});
|
||||||
|
state.maybe_d2d.?.deinit();
|
||||||
|
state.maybe_d2d = null;
|
||||||
|
win32.invalidateHwnd(hwnd);
|
||||||
|
} else if (err.hr < 0) std.debug.panic("paint error: {}", .{err});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paintD2d(
|
||||||
|
d2d: *const D2d,
|
||||||
|
screen: *const vaxis.Screen,
|
||||||
|
font: Font,
|
||||||
|
) void {
|
||||||
|
d2d.target.ID2D1RenderTarget.Clear(&global.background);
|
||||||
|
for (0..screen.height) |y| {
|
||||||
|
const row_y: i32 = font.cell_size.y * @as(i32, @intCast(y));
|
||||||
|
for (0..screen.width) |x| {
|
||||||
|
const column_x: i32 = font.cell_size.x * @as(i32, @intCast(x));
|
||||||
|
const cell_index = screen.width * y + x;
|
||||||
|
const cell = &screen.buf[cell_index];
|
||||||
|
|
||||||
|
const cell_rect: win32.RECT = .{
|
||||||
|
.left = column_x,
|
||||||
|
.top = row_y,
|
||||||
|
.right = column_x + font.cell_size.x,
|
||||||
|
.bottom = row_y + font.cell_size.y,
|
||||||
|
};
|
||||||
|
ddui.FillRectangle(
|
||||||
|
&d2d.target.ID2D1RenderTarget,
|
||||||
|
cell_rect,
|
||||||
|
d2d.solid(d2dColorFromVAxis(cell.style.bg)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: pre-caclulate the buffer size needed, for now this should just
|
||||||
|
// cause out-of-bounds access
|
||||||
|
var buf_wtf16: [100]u16 = undefined;
|
||||||
|
const grapheme_len = blk: {
|
||||||
|
break :blk std.unicode.wtf8ToWtf16Le(&buf_wtf16, cell.char.grapheme) catch |err| switch (err) {
|
||||||
|
error.InvalidWtf8 => {
|
||||||
|
buf_wtf16[0] = std.unicode.replacement_character;
|
||||||
|
break :blk 1;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const grapheme = buf_wtf16[0..grapheme_len];
|
||||||
|
if (std.mem.eql(u16, grapheme, &[_]u16{' '}))
|
||||||
|
continue;
|
||||||
|
ddui.DrawText(
|
||||||
|
&d2d.target.ID2D1RenderTarget,
|
||||||
|
grapheme,
|
||||||
|
font.text_format,
|
||||||
|
ddui.rectFloatFromInt(cell_rect),
|
||||||
|
d2d.solid(d2dColorFromVAxis(cell.style.fg)),
|
||||||
|
.{
|
||||||
|
.CLIP = 1,
|
||||||
|
.ENABLE_COLOR_FONT = 1,
|
||||||
|
},
|
||||||
|
.NATURAL,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn d2dColorFromVAxis(color: vaxis.Cell.Color) win32.D2D_COLOR_F {
|
||||||
|
return switch (color) {
|
||||||
|
.default => .{ .r = 0, .g = 0, .b = 0, .a = 0 },
|
||||||
|
.index => |idx| blk: {
|
||||||
|
const rgb = RGB.from_u24(xterm.colors[idx]);
|
||||||
|
break :blk .{
|
||||||
|
.r = @as(f32, @floatFromInt(rgb.r)) / 255.0,
|
||||||
|
.g = @as(f32, @floatFromInt(rgb.g)) / 255.0,
|
||||||
|
.b = @as(f32, @floatFromInt(rgb.b)) / 255.0,
|
||||||
|
.a = 1,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
.rgb => |rgb| .{
|
||||||
|
.r = @as(f32, @floatFromInt(rgb[0])) / 255.0,
|
||||||
|
.g = @as(f32, @floatFromInt(rgb[1])) / 255.0,
|
||||||
|
.b = @as(f32, @floatFromInt(rgb[2])) / 255.0,
|
||||||
|
.a = 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getClientSize(hwnd: win32.HWND) XY(i32) {
|
||||||
|
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 = rect.right, .y = 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)) });
|
||||||
|
}
|
801
src/win32/d3d11.zig
Normal file
801
src/win32/d3d11.zig
Normal file
|
@ -0,0 +1,801 @@
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const std = @import("std");
|
||||||
|
const win32 = @import("win32").everything;
|
||||||
|
const win32ext = @import("win32ext.zig");
|
||||||
|
const vaxis = @import("vaxis");
|
||||||
|
|
||||||
|
const dwrite = @import("dwrite.zig");
|
||||||
|
const GlyphIndexCache = @import("GlyphIndexCache.zig");
|
||||||
|
const TextRenderer = @import("DwriteRenderer.zig");
|
||||||
|
|
||||||
|
const RGB = @import("color").RGB;
|
||||||
|
const xterm = @import("xterm.zig");
|
||||||
|
const XY = @import("xy.zig").XY;
|
||||||
|
|
||||||
|
pub const Font = dwrite.Font;
|
||||||
|
|
||||||
|
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 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 glyph_cache_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
||||||
|
var text_renderer: TextRenderer = undefined;
|
||||||
|
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 };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// types shared with the shader
|
||||||
|
const shader = struct {
|
||||||
|
const GridConfig = extern struct {
|
||||||
|
cell_size: [2]u32,
|
||||||
|
col_count: u32,
|
||||||
|
row_count: u32,
|
||||||
|
};
|
||||||
|
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() void {
|
||||||
|
std.debug.assert(!global.init_called);
|
||||||
|
global.init_called = true;
|
||||||
|
dwrite.init();
|
||||||
|
|
||||||
|
const debug_d3d = switch (builtin.mode) {
|
||||||
|
.Debug => true,
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
global.d3d = D3d.init(.{ .debug = debug_d3d });
|
||||||
|
|
||||||
|
if (debug_d3d) {
|
||||||
|
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();
|
||||||
|
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
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, rgb: RGB) void {
|
||||||
|
global.background = .{ .r = rgb.r, .g = rgb.b, .b = rgb.b, .a = 255 };
|
||||||
|
const color: win32.DXGI_RGBA = .{
|
||||||
|
.r = @as(f32, @floatFromInt(rgb.r)) / 255,
|
||||||
|
.g = @as(f32, @floatFromInt(rgb.g)) / 255,
|
||||||
|
.b = @as(f32, @floatFromInt(rgb.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 };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn paint(
|
||||||
|
hwnd: win32.HWND,
|
||||||
|
state: *WindowState,
|
||||||
|
font: Font,
|
||||||
|
screen: *const vaxis.Screen,
|
||||||
|
) void {
|
||||||
|
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 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) {
|
||||||
|
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,
|
||||||
|
@intCast(client_size.x),
|
||||||
|
@intCast(client_size.y),
|
||||||
|
.UNKNOWN,
|
||||||
|
swap_chain_flags,
|
||||||
|
);
|
||||||
|
if (hr < 0) fatalHr("ResizeBuffers", hr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.?);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 space_glyph = generateGlyph(
|
||||||
|
font,
|
||||||
|
glyph_index_cache,
|
||||||
|
texture_cell_count.x,
|
||||||
|
" ",
|
||||||
|
state.glyph_texture.obj,
|
||||||
|
);
|
||||||
|
const populate_col_count: u16 = @min(screen.width, shader_col_count);
|
||||||
|
const populate_row_count: u16 = @min(screen.height, shader_row_count);
|
||||||
|
// we loop through and cache all the glyphs before mapping the cell buffer and potentially
|
||||||
|
// blocking the gpu while we're doing expensive text rendering
|
||||||
|
for (0..populate_row_count) |row| {
|
||||||
|
const row_offset = row * screen.width;
|
||||||
|
for (0..populate_col_count) |col| {
|
||||||
|
const screen_cell = &screen.buf[row_offset + col];
|
||||||
|
_ = generateGlyph(
|
||||||
|
font,
|
||||||
|
glyph_index_cache,
|
||||||
|
texture_cell_count.x,
|
||||||
|
screen_cell.char.grapheme,
|
||||||
|
state.glyph_texture.obj,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_offset = row * screen.width;
|
||||||
|
const dst_row_offset = row * @as(usize, shader_col_count);
|
||||||
|
const src_col_count = if (row < screen.height) populate_col_count else 0;
|
||||||
|
for (0..src_col_count) |col| {
|
||||||
|
const screen_cell = &screen.buf[src_row_offset + col];
|
||||||
|
const codepoint = std.unicode.wtf8Decode(screen_cell.char.grapheme) catch std.unicode.replacement_character;
|
||||||
|
const glyph_index = blk: {
|
||||||
|
switch (glyph_index_cache.reserve(global.glyph_cache_arena.allocator(), codepoint) catch |e| oom(e)) {
|
||||||
|
.newly_reserved => |reserved| {
|
||||||
|
// should never happen unless there' more characters than the cache can hold
|
||||||
|
// 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);
|
||||||
|
global.text_renderer.render(
|
||||||
|
global.d3d.device,
|
||||||
|
global.d3d.context,
|
||||||
|
global.d2d_factory,
|
||||||
|
font,
|
||||||
|
state.glyph_texture.obj,
|
||||||
|
codepoint,
|
||||||
|
coord,
|
||||||
|
);
|
||||||
|
break :blk reserved.index;
|
||||||
|
},
|
||||||
|
.already_reserved => |i| break :blk i,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
cells_shader[dst_row_offset + col] = .{
|
||||||
|
.glyph_index = glyph_index,
|
||||||
|
.background = shaderColorFromVaxis(screen_cell.style.bg),
|
||||||
|
.foreground = shaderColorFromVaxis(screen_cell.style.fg),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
for (src_col_count..shader_col_count) |col| {
|
||||||
|
cells_shader[dst_row_offset + col] = .{
|
||||||
|
.glyph_index = space_glyph,
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generateGlyph(
|
||||||
|
font: Font,
|
||||||
|
glyph_index_cache: *GlyphIndexCache,
|
||||||
|
texture_column_count: u16,
|
||||||
|
grapheme_utf8: []const u8,
|
||||||
|
texture: *win32.ID3D11Texture2D,
|
||||||
|
) u32 {
|
||||||
|
const codepoint = std.unicode.wtf8Decode(grapheme_utf8) catch std.unicode.replacement_character;
|
||||||
|
switch (glyph_index_cache.reserve(
|
||||||
|
global.glyph_cache_arena.allocator(),
|
||||||
|
codepoint,
|
||||||
|
) 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_column_count);
|
||||||
|
const coord = coordFromCellPos(font.cell_size, pos);
|
||||||
|
global.text_renderer.render(
|
||||||
|
global.d3d.device,
|
||||||
|
global.d3d.context,
|
||||||
|
global.d2d_factory,
|
||||||
|
font,
|
||||||
|
texture,
|
||||||
|
codepoint,
|
||||||
|
coord,
|
||||||
|
);
|
||||||
|
return reserved.index;
|
||||||
|
},
|
||||||
|
.already_reserved => |index| return index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shaderColorFromVaxis(color: vaxis.Color) Rgba8 {
|
||||||
|
return switch (color) {
|
||||||
|
.default => .{ .r = 0, .g = 0, .b = 0, .a = 255 },
|
||||||
|
.index => |idx| return @bitCast(@as(u32, xterm.colors[idx]) << 8 | 0xff),
|
||||||
|
.rgb => |rgb| .{ .r = rgb[0], .g = rgb[1], .b = rgb[2], .a = 255 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const D3d = struct {
|
||||||
|
device: *win32.ID3D11Device,
|
||||||
|
context: *win32.ID3D11DeviceContext,
|
||||||
|
context1: *win32.ID3D11DeviceContext1,
|
||||||
|
|
||||||
|
pub fn init(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 = win32ext.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 = 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() Shaders {
|
||||||
|
const shader_source = @embedFile("terminal.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),
|
||||||
|
);
|
||||||
|
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 = .B8G8R8A8_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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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) {
|
||||||
|
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
// small size so we can just render the whole texture for development
|
||||||
|
//if (true) return .{ .x = 80, .y = 500 };
|
||||||
|
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));
|
||||||
|
}
|
96
src/win32/dwrite.zig
Normal file
96
src/win32/dwrite.zig
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const win32 = @import("win32").everything;
|
||||||
|
|
||||||
|
const FontFace = @import("FontFace.zig");
|
||||||
|
const XY = @import("xy.zig").XY;
|
||||||
|
|
||||||
|
const global = struct {
|
||||||
|
var init_called: bool = false;
|
||||||
|
var dwrite_factory: *win32.IDWriteFactory = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init() void {
|
||||||
|
std.debug.assert(!global.init_called);
|
||||||
|
global.init_called = true;
|
||||||
|
{
|
||||||
|
const hr = win32.DWriteCreateFactory(
|
||||||
|
win32.DWRITE_FACTORY_TYPE_SHARED,
|
||||||
|
win32.IID_IDWriteFactory,
|
||||||
|
@ptrCast(&global.dwrite_factory),
|
||||||
|
);
|
||||||
|
if (hr < 0) fatalHr("DWriteCreateFactory", hr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Font = struct {
|
||||||
|
text_format: *win32.IDWriteTextFormat,
|
||||||
|
cell_size: XY(u16),
|
||||||
|
|
||||||
|
pub fn init(dpi: u32, size: f32, face: *const FontFace) Font {
|
||||||
|
var text_format: *win32.IDWriteTextFormat = undefined;
|
||||||
|
{
|
||||||
|
const hr = global.dwrite_factory.CreateTextFormat(
|
||||||
|
face.ptr(),
|
||||||
|
null,
|
||||||
|
.NORMAL, //weight
|
||||||
|
.NORMAL, // style
|
||||||
|
.NORMAL, // stretch
|
||||||
|
win32.scaleDpi(f32, size, dpi),
|
||||||
|
win32.L(""), // locale
|
||||||
|
&text_format,
|
||||||
|
);
|
||||||
|
if (hr < 0) std.debug.panic(
|
||||||
|
"CreateTextFormat '{}' height {d} failed, hresult=0x{x}",
|
||||||
|
.{ std.unicode.fmtUtf16le(face.slice()), size, @as(u32, @bitCast(hr)) },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
errdefer _ = text_format.IUnknown.Release();
|
||||||
|
|
||||||
|
const cell_size = blk: {
|
||||||
|
var text_layout: *win32.IDWriteTextLayout = undefined;
|
||||||
|
{
|
||||||
|
const hr = global.dwrite_factory.CreateTextLayout(
|
||||||
|
win32.L("█"),
|
||||||
|
1,
|
||||||
|
text_format,
|
||||||
|
std.math.floatMax(f32),
|
||||||
|
std.math.floatMax(f32),
|
||||||
|
&text_layout,
|
||||||
|
);
|
||||||
|
if (hr < 0) fatalHr("CreateTextLayout", hr);
|
||||||
|
}
|
||||||
|
defer _ = text_layout.IUnknown.Release();
|
||||||
|
|
||||||
|
var metrics: win32.DWRITE_TEXT_METRICS = undefined;
|
||||||
|
{
|
||||||
|
const hr = text_layout.GetMetrics(&metrics);
|
||||||
|
if (hr < 0) fatalHr("GetMetrics", hr);
|
||||||
|
}
|
||||||
|
break :blk .{
|
||||||
|
.x = @as(u16, @intFromFloat(@floor(metrics.width))),
|
||||||
|
.y = @as(u16, @intFromFloat(@floor(metrics.height))),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.text_format = text_format,
|
||||||
|
.cell_size = cell_size,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Font) void {
|
||||||
|
_ = self.text_format.IUnknown.Release();
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getCellSize(self: Font, comptime T: type) XY(T) {
|
||||||
|
return .{
|
||||||
|
.x = @intCast(self.cell_size.x),
|
||||||
|
.y = @intCast(self.cell_size.y),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn fatalHr(what: []const u8, hresult: win32.HRESULT) noreturn {
|
||||||
|
std.debug.panic("{s} failed, hresult=0x{x}", .{ what, @as(u32, @bitCast(hresult)) });
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const build_options = @import("build_options");
|
||||||
const root = @import("root");
|
const root = @import("root");
|
||||||
|
|
||||||
const c = @cImport({
|
const c = @cImport({
|
||||||
|
@ -6,7 +7,7 @@ const c = @cImport({
|
||||||
});
|
});
|
||||||
|
|
||||||
const win32 = @import("win32").everything;
|
const win32 = @import("win32").everything;
|
||||||
const ddui = @import("ddui");
|
const win32ext = @import("win32ext.zig");
|
||||||
|
|
||||||
const cbor = @import("cbor");
|
const cbor = @import("cbor");
|
||||||
const thespian = @import("thespian");
|
const thespian = @import("thespian");
|
||||||
|
@ -17,19 +18,24 @@ const RGB = @import("color").RGB;
|
||||||
const input = @import("input");
|
const input = @import("input");
|
||||||
const windowmsg = @import("windowmsg.zig");
|
const windowmsg = @import("windowmsg.zig");
|
||||||
|
|
||||||
const HResultError = ddui.HResultError;
|
const render = if (build_options.d2d) @import("d2d.zig") else @import("d3d11.zig");
|
||||||
|
|
||||||
|
const FontFace = @import("FontFace.zig");
|
||||||
|
const XY = @import("xy.zig").XY;
|
||||||
|
|
||||||
const WM_APP_EXIT = win32.WM_APP + 1;
|
const WM_APP_EXIT = win32.WM_APP + 1;
|
||||||
const WM_APP_SET_BACKGROUND = win32.WM_APP + 2;
|
const WM_APP_SET_BACKGROUND = win32.WM_APP + 2;
|
||||||
const WM_APP_ADJUST_FONTSIZE = win32.WM_APP + 3;
|
const WM_APP_ADJUST_FONTSIZE = win32.WM_APP + 3;
|
||||||
const WM_APP_SET_FONTSIZE = win32.WM_APP + 4;
|
const WM_APP_SET_FONTSIZE = win32.WM_APP + 4;
|
||||||
const WM_APP_SET_FONTFACE = win32.WM_APP + 5;
|
const WM_APP_SET_FONTFACE = win32.WM_APP + 5;
|
||||||
|
const WM_APP_UPDATE_SCREEN = win32.WM_APP + 6;
|
||||||
|
|
||||||
const WM_APP_EXIT_RESULT = 0x45feaa11;
|
const WM_APP_EXIT_RESULT = 0x45feaa11;
|
||||||
const WM_APP_SET_BACKGROUND_RESULT = 0x369a26cd;
|
const WM_APP_SET_BACKGROUND_RESULT = 0x369a26cd;
|
||||||
const WM_APP_ADJUST_FONTSIZE_RESULT = 0x79aba9ef;
|
const WM_APP_ADJUST_FONTSIZE_RESULT = 0x79aba9ef;
|
||||||
const WM_APP_SET_FONTSIZE_RESULT = 0x72fa44bc;
|
const WM_APP_SET_FONTSIZE_RESULT = 0x72fa44bc;
|
||||||
const WM_APP_SET_FONTFACE_RESULT = 0x1a49ffa8;
|
const WM_APP_SET_FONTFACE_RESULT = 0x1a49ffa8;
|
||||||
|
const WM_APP_UPDATE_SCREEN_RESULT = 0x3add213b;
|
||||||
|
|
||||||
pub const DropWriter = struct {
|
pub const DropWriter = struct {
|
||||||
pub const WriteError = error{};
|
pub const WriteError = error{};
|
||||||
|
@ -58,48 +64,27 @@ const global = struct {
|
||||||
var init_called: bool = false;
|
var init_called: bool = false;
|
||||||
var start_called: bool = false;
|
var start_called: bool = false;
|
||||||
var icons: Icons = undefined;
|
var icons: Icons = undefined;
|
||||||
var dwrite_factory: *win32.IDWriteFactory = undefined;
|
|
||||||
var state: ?State = null;
|
var state: ?State = null;
|
||||||
var d2d_factory: *win32.ID2D1Factory = undefined;
|
|
||||||
var conf: ?gui_config = null;
|
var conf: ?gui_config = null;
|
||||||
var fontface: ?[:0]const u16 = null;
|
var fontface: ?FontFace = null;
|
||||||
var fontsize: ?f32 = null;
|
var fontsize: ?f32 = null;
|
||||||
|
var font: ?Font = null;
|
||||||
|
|
||||||
var text_format_editor: ddui.TextFormatCache(FontCacheParams, createTextFormatEditor) = .{};
|
var screen_arena: std.heap.ArenaAllocator = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
||||||
|
var screen: vaxis.Screen = .{};
|
||||||
const shared_screen = struct {
|
|
||||||
var mutex: std.Thread.Mutex = .{};
|
|
||||||
// only access arena/obj while the mutex is locked
|
|
||||||
var arena: std.heap.ArenaAllocator = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
|
||||||
var obj: vaxis.Screen = .{};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
const window_style_ex = win32.WINDOW_EX_STYLE{
|
const window_style_ex = win32.WINDOW_EX_STYLE{
|
||||||
.APPWINDOW = 1,
|
.APPWINDOW = 1,
|
||||||
//.ACCEPTFILES = 1,
|
//.ACCEPTFILES = 1,
|
||||||
|
.NOREDIRECTIONBITMAP = render.NOREDIRECTIONBITMAP,
|
||||||
};
|
};
|
||||||
const window_style = win32.WS_OVERLAPPEDWINDOW;
|
const window_style = win32.WS_OVERLAPPEDWINDOW;
|
||||||
|
|
||||||
pub fn init() void {
|
pub fn init() void {
|
||||||
std.debug.assert(!global.init_called);
|
std.debug.assert(!global.init_called);
|
||||||
global.init_called = true;
|
global.init_called = true;
|
||||||
|
render.init();
|
||||||
{
|
|
||||||
const hr = win32.DWriteCreateFactory(
|
|
||||||
win32.DWRITE_FACTORY_TYPE_SHARED,
|
|
||||||
win32.IID_IDWriteFactory,
|
|
||||||
@ptrCast(&global.dwrite_factory),
|
|
||||||
);
|
|
||||||
if (hr < 0) fatalHr("DWriteCreateFactory", hr);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
var err: HResultError = undefined;
|
|
||||||
global.d2d_factory = ddui.createFactory(
|
|
||||||
.SINGLE_THREADED,
|
|
||||||
.{},
|
|
||||||
&err,
|
|
||||||
) catch std.debug.panic("{}", .{err});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Icons = struct {
|
const Icons = struct {
|
||||||
|
@ -135,35 +120,6 @@ fn getIcons(dpi: XY(u32)) Icons {
|
||||||
return .{ .small = @ptrCast(small), .large = @ptrCast(large) };
|
return .{ .small = @ptrCast(small), .large = @ptrCast(large) };
|
||||||
}
|
}
|
||||||
|
|
||||||
fn d2dColorFromVAxis(color: vaxis.Cell.Color) win32.D2D_COLOR_F {
|
|
||||||
return switch (color) {
|
|
||||||
.default => .{ .r = 0, .g = 0, .b = 0, .a = 0 },
|
|
||||||
.index => |idx| blk: {
|
|
||||||
const rgb = RGB.from_u24(xterm_colors[idx]);
|
|
||||||
break :blk .{
|
|
||||||
.r = @as(f32, @floatFromInt(rgb.r)) / 255.0,
|
|
||||||
.g = @as(f32, @floatFromInt(rgb.g)) / 255.0,
|
|
||||||
.b = @as(f32, @floatFromInt(rgb.b)) / 255.0,
|
|
||||||
.a = 1,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
.rgb => |rgb| .{
|
|
||||||
.r = @as(f32, @floatFromInt(rgb[0])) / 255.0,
|
|
||||||
.g = @as(f32, @floatFromInt(rgb[1])) / 255.0,
|
|
||||||
.b = @as(f32, @floatFromInt(rgb[2])) / 255.0,
|
|
||||||
.a = 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const FontCacheParams = struct {
|
|
||||||
dpi: u32,
|
|
||||||
fontsize: f32,
|
|
||||||
pub fn eql(self: FontCacheParams, other: FontCacheParams) bool {
|
|
||||||
return self.dpi == other.dpi and self.fontsize == other.fontsize;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fn getConfig() *const gui_config {
|
fn getConfig() *const gui_config {
|
||||||
if (global.conf == null) {
|
if (global.conf == null) {
|
||||||
global.conf, _ = root.read_config(gui_config, global.arena);
|
global.conf, _ = root.read_config(gui_config, global.arena);
|
||||||
|
@ -177,20 +133,34 @@ fn getFieldDefault(field: std.builtin.Type.StructField) ?*const field.type {
|
||||||
return @alignCast(@ptrCast(field.default_value orelse return null));
|
return @alignCast(@ptrCast(field.default_value orelse return null));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getFontFace() [:0]const u16 {
|
fn getDefaultFontFace() FontFace {
|
||||||
|
const default = comptime getFieldDefault(
|
||||||
|
std.meta.fieldInfo(gui_config, .fontface),
|
||||||
|
) orelse @compileError("gui_config fontface is missing default");
|
||||||
|
const default_wide = win32.L(default.*);
|
||||||
|
var result: FontFace = .{ .buf = undefined, .len = default_wide.len };
|
||||||
|
@memcpy(result.buf[0..default_wide.len], default_wide);
|
||||||
|
result.buf[default_wide.len] = 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getFontFace() *const FontFace {
|
||||||
if (global.fontface == null) {
|
if (global.fontface == null) {
|
||||||
const conf = getConfig();
|
const conf = getConfig();
|
||||||
global.fontface = blk: {
|
global.fontface = blk: {
|
||||||
break :blk std.unicode.utf8ToUtf16LeAllocZ(std.heap.c_allocator, conf.fontface) catch |e| {
|
break :blk FontFace.initUtf8(conf.fontface) catch |e| switch (e) {
|
||||||
std.log.err("failed to convert fontface name with {s}", .{@errorName(e)});
|
error.TooLong => {
|
||||||
const default = comptime getFieldDefault(
|
std.log.err("fontface '{s}' is too long", .{conf.fontface});
|
||||||
std.meta.fieldInfo(gui_config, .fontface),
|
break :blk getDefaultFontFace();
|
||||||
) orelse @compileError("fontface is missing default");
|
},
|
||||||
break :blk win32.L(default.*);
|
error.InvalidUtf8 => {
|
||||||
|
std.log.err("fontface '{s}' is invalid utf8", .{conf.fontface});
|
||||||
|
break :blk getDefaultFontFace();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return global.fontface.?;
|
return &(global.fontface.?);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getFontSize() f32 {
|
fn getFontSize() f32 {
|
||||||
|
@ -200,88 +170,34 @@ fn getFontSize() f32 {
|
||||||
return global.fontsize.?;
|
return global.fontsize.?;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn createTextFormatEditor(params: FontCacheParams) *win32.IDWriteTextFormat {
|
fn getFont(dpi: u32, size: f32, face: *const FontFace) render.Font {
|
||||||
var err: HResultError = undefined;
|
if (global.font) |*font| {
|
||||||
return ddui.createTextFormat(global.dwrite_factory, &err, .{
|
if (font.dpi == dpi and font.size == size and font.face.eql(face))
|
||||||
.size = win32.scaleDpi(f32, params.fontsize, params.dpi),
|
return font.render_object;
|
||||||
.family_name = getFontFace(),
|
font.render_object.deinit();
|
||||||
}) catch std.debug.panic("{s} failed, hresult=0x{x}", .{ err.context, err.hr });
|
global.font = null;
|
||||||
|
}
|
||||||
|
global.font = .{
|
||||||
|
.dpi = dpi,
|
||||||
|
.size = size,
|
||||||
|
.face = face.*,
|
||||||
|
.render_object = render.Font.init(dpi, size, face),
|
||||||
|
};
|
||||||
|
return global.font.?.render_object;
|
||||||
}
|
}
|
||||||
|
|
||||||
const D2d = struct {
|
const Font = struct {
|
||||||
target: *win32.ID2D1HwndRenderTarget,
|
dpi: u32,
|
||||||
brush: *win32.ID2D1SolidColorBrush,
|
size: f32,
|
||||||
pub fn init(hwnd: win32.HWND, err: *HResultError) error{HResult}!D2d {
|
face: FontFace,
|
||||||
var target: *win32.ID2D1HwndRenderTarget = undefined;
|
render_object: render.Font,
|
||||||
const target_props = win32.D2D1_RENDER_TARGET_PROPERTIES{
|
|
||||||
.type = .DEFAULT,
|
|
||||||
.pixelFormat = .{
|
|
||||||
.format = .B8G8R8A8_UNORM,
|
|
||||||
.alphaMode = .PREMULTIPLIED,
|
|
||||||
},
|
|
||||||
.dpiX = 0,
|
|
||||||
.dpiY = 0,
|
|
||||||
.usage = .{},
|
|
||||||
.minLevel = .DEFAULT,
|
|
||||||
};
|
|
||||||
const hwnd_target_props = win32.D2D1_HWND_RENDER_TARGET_PROPERTIES{
|
|
||||||
.hwnd = hwnd,
|
|
||||||
.pixelSize = .{ .width = 0, .height = 0 },
|
|
||||||
.presentOptions = .{},
|
|
||||||
};
|
|
||||||
|
|
||||||
{
|
|
||||||
const hr = global.d2d_factory.CreateHwndRenderTarget(
|
|
||||||
&target_props,
|
|
||||||
&hwnd_target_props,
|
|
||||||
&target,
|
|
||||||
);
|
|
||||||
if (hr < 0) return err.set(hr, "CreateHwndRenderTarget");
|
|
||||||
}
|
|
||||||
errdefer _ = target.IUnknown.Release();
|
|
||||||
|
|
||||||
{
|
|
||||||
var dc: *win32.ID2D1DeviceContext = undefined;
|
|
||||||
{
|
|
||||||
const hr = target.IUnknown.QueryInterface(win32.IID_ID2D1DeviceContext, @ptrCast(&dc));
|
|
||||||
if (hr < 0) return err.set(hr, "GetDeviceContext");
|
|
||||||
}
|
|
||||||
defer _ = dc.IUnknown.Release();
|
|
||||||
// just make everything DPI aware, all applications should just do this
|
|
||||||
dc.SetUnitMode(win32.D2D1_UNIT_MODE_PIXELS);
|
|
||||||
}
|
|
||||||
|
|
||||||
var brush: *win32.ID2D1SolidColorBrush = undefined;
|
|
||||||
{
|
|
||||||
const color: win32.D2D_COLOR_F = .{ .r = 0, .g = 0, .b = 0, .a = 0 };
|
|
||||||
const hr = target.ID2D1RenderTarget.CreateSolidColorBrush(&color, null, &brush);
|
|
||||||
if (hr < 0) return err.set(hr, "CreateSolidBrush");
|
|
||||||
}
|
|
||||||
errdefer _ = brush.IUnknown.Release();
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.target = target,
|
|
||||||
.brush = brush,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn deinit(self: *D2d) void {
|
|
||||||
_ = self.brush.IUnknown.Release();
|
|
||||||
_ = self.target.IUnknown.Release();
|
|
||||||
}
|
|
||||||
pub fn solid(self: *const D2d, color: win32.D2D_COLOR_F) *win32.ID2D1Brush {
|
|
||||||
self.brush.SetColor(&color);
|
|
||||||
return &self.brush.ID2D1Brush;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const State = struct {
|
const State = struct {
|
||||||
hwnd: win32.HWND,
|
hwnd: win32.HWND,
|
||||||
pid: thespian.pid,
|
pid: thespian.pid,
|
||||||
maybe_d2d: ?D2d = null,
|
render_state: render.WindowState,
|
||||||
erase_bg_done: bool = false,
|
|
||||||
scroll_delta: isize = 0,
|
scroll_delta: isize = 0,
|
||||||
currently_rendered_cell_size: ?XY(i32) = null,
|
|
||||||
background: ?u32 = null,
|
|
||||||
last_sizing_edge: ?win32.WPARAM = null,
|
last_sizing_edge: ?win32.WPARAM = null,
|
||||||
bounds: ?WindowBounds = null,
|
bounds: ?WindowBounds = null,
|
||||||
};
|
};
|
||||||
|
@ -290,67 +206,6 @@ fn stateFromHwnd(hwnd: win32.HWND) *State {
|
||||||
return &global.state.?;
|
return &global.state.?;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(
|
|
||||||
d2d: *const D2d,
|
|
||||||
background: RGB,
|
|
||||||
screen: *const vaxis.Screen,
|
|
||||||
text_format_editor: *win32.IDWriteTextFormat,
|
|
||||||
cell_size: XY(i32),
|
|
||||||
) void {
|
|
||||||
{
|
|
||||||
const color = ddui.rgb8(background.r, background.g, background.b);
|
|
||||||
d2d.target.ID2D1RenderTarget.Clear(&color);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (0..screen.height) |y| {
|
|
||||||
const row_y: i32 = cell_size.y * @as(i32, @intCast(y));
|
|
||||||
for (0..screen.width) |x| {
|
|
||||||
const column_x: i32 = cell_size.x * @as(i32, @intCast(x));
|
|
||||||
const cell_index = screen.width * y + x;
|
|
||||||
const cell = &screen.buf[cell_index];
|
|
||||||
|
|
||||||
const cell_rect: win32.RECT = .{
|
|
||||||
.left = column_x,
|
|
||||||
.top = row_y,
|
|
||||||
.right = column_x + cell_size.x,
|
|
||||||
.bottom = row_y + cell_size.y,
|
|
||||||
};
|
|
||||||
ddui.FillRectangle(
|
|
||||||
&d2d.target.ID2D1RenderTarget,
|
|
||||||
cell_rect,
|
|
||||||
d2d.solid(d2dColorFromVAxis(cell.style.bg)),
|
|
||||||
);
|
|
||||||
|
|
||||||
// TODO: pre-caclulate the buffer size needed, for now this should just
|
|
||||||
// cause out-of-bounds access
|
|
||||||
var buf_wtf16: [100]u16 = undefined;
|
|
||||||
const grapheme_len = blk: {
|
|
||||||
break :blk std.unicode.wtf8ToWtf16Le(&buf_wtf16, cell.char.grapheme) catch |err| switch (err) {
|
|
||||||
error.InvalidWtf8 => {
|
|
||||||
buf_wtf16[0] = std.unicode.replacement_character;
|
|
||||||
break :blk 1;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
const grapheme = buf_wtf16[0..grapheme_len];
|
|
||||||
if (std.mem.eql(u16, grapheme, &[_]u16{' '}))
|
|
||||||
continue;
|
|
||||||
ddui.DrawText(
|
|
||||||
&d2d.target.ID2D1RenderTarget,
|
|
||||||
grapheme,
|
|
||||||
text_format_editor,
|
|
||||||
ddui.rectFloatFromInt(cell_rect),
|
|
||||||
d2d.solid(d2dColorFromVAxis(cell.style.fg)),
|
|
||||||
.{
|
|
||||||
.CLIP = 1,
|
|
||||||
.ENABLE_COLOR_FONT = 1,
|
|
||||||
},
|
|
||||||
.NATURAL,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const WindowPlacement = struct {
|
const WindowPlacement = struct {
|
||||||
dpi: XY(u32),
|
dpi: XY(u32),
|
||||||
size: XY(i32),
|
size: XY(i32),
|
||||||
|
@ -409,7 +264,7 @@ fn calcWindowPlacement(
|
||||||
.x = @min(wanted_size.x, work_size.x),
|
.x = @min(wanted_size.x, work_size.x),
|
||||||
.y = @min(wanted_size.y, work_size.y),
|
.y = @min(wanted_size.y, work_size.y),
|
||||||
};
|
};
|
||||||
const bouding_rect: win32.RECT = ddui.rectIntFromSize(.{
|
const bouding_rect: win32.RECT = rectIntFromSize(.{
|
||||||
.left = work_rect.left + @divTrunc(work_size.x - bounding_size.x, 2),
|
.left = work_rect.left + @divTrunc(work_size.x - bounding_size.x, 2),
|
||||||
.top = work_rect.top + @divTrunc(work_size.y - bounding_size.y, 2),
|
.top = work_rect.top + @divTrunc(work_size.y - bounding_size.y, 2),
|
||||||
.width = bounding_size.x,
|
.width = bounding_size.x,
|
||||||
|
@ -440,6 +295,9 @@ pub fn start() !std.Thread {
|
||||||
return try std.Thread.spawn(.{}, entry, .{pid});
|
return try std.Thread.spawn(.{}, entry, .{pid});
|
||||||
}
|
}
|
||||||
fn entry(pid: thespian.pid) !void {
|
fn entry(pid: thespian.pid) !void {
|
||||||
|
std.debug.assert(global.init_called);
|
||||||
|
std.debug.assert(global.start_called);
|
||||||
|
|
||||||
const conf = getConfig();
|
const conf = getConfig();
|
||||||
|
|
||||||
const maybe_monitor: ?win32.HMONITOR = blk: {
|
const maybe_monitor: ?win32.HMONITOR = blk: {
|
||||||
|
@ -472,8 +330,7 @@ fn entry(pid: thespian.pid) !void {
|
||||||
break :blk dpi;
|
break :blk dpi;
|
||||||
};
|
};
|
||||||
|
|
||||||
const text_format_editor = global.text_format_editor.getOrCreate(FontCacheParams{ .dpi = @max(dpi.x, dpi.y), .fontsize = @floatFromInt(conf.fontsize) });
|
const cell_size = getFont(@max(dpi.x, dpi.y), getFontSize(), getFontFace()).getCellSize(i32);
|
||||||
const cell_size = getCellSize(text_format_editor);
|
|
||||||
const initial_placement = calcWindowPlacement(
|
const initial_placement = calcWindowPlacement(
|
||||||
maybe_monitor,
|
maybe_monitor,
|
||||||
@max(dpi.x, dpi.y),
|
@max(dpi.x, dpi.y),
|
||||||
|
@ -598,45 +455,25 @@ pub fn set_fontsize(hwnd: win32.HWND, fontsize: f32) void {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_fontface(hwnd: win32.HWND, fontface_utf8: []const u8) void {
|
pub fn set_fontface(hwnd: win32.HWND, fontface_utf8: []const u8) void {
|
||||||
const fontface = std.unicode.utf8ToUtf16LeAllocZ(std.heap.c_allocator, fontface_utf8) catch |e| {
|
const fontface = FontFace.initUtf8(fontface_utf8) catch |e| {
|
||||||
std.log.err("failed to convert fontface name '{s}' with {s}", .{ fontface_utf8, @errorName(e) });
|
std.log.err("failed to set fontface '{s}' with {s}", .{ fontface_utf8, @errorName(e) });
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
std.debug.assert(WM_APP_SET_FONTFACE_RESULT == win32.SendMessageW(
|
std.debug.assert(WM_APP_SET_FONTFACE_RESULT == win32.SendMessageW(
|
||||||
hwnd,
|
hwnd,
|
||||||
WM_APP_SET_FONTFACE,
|
WM_APP_SET_FONTFACE,
|
||||||
@intFromPtr(fontface.ptr),
|
@intFromPtr(&fontface),
|
||||||
@intCast(fontface.len),
|
0,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn updateScreen(screen: *const vaxis.Screen) void {
|
pub fn updateScreen(hwnd: win32.HWND, screen: *const vaxis.Screen) void {
|
||||||
global.shared_screen.mutex.lock();
|
std.debug.assert(WM_APP_UPDATE_SCREEN_RESULT == win32.SendMessageW(
|
||||||
defer global.shared_screen.mutex.unlock();
|
hwnd,
|
||||||
_ = global.shared_screen.arena.reset(.retain_capacity);
|
WM_APP_UPDATE_SCREEN,
|
||||||
|
@intFromPtr(screen),
|
||||||
const buf = global.shared_screen.arena.allocator().alloc(vaxis.Cell, screen.buf.len) catch |e| oom(e);
|
0,
|
||||||
@memcpy(buf, screen.buf);
|
));
|
||||||
for (buf) |*cell| {
|
|
||||||
cell.char.grapheme = global.shared_screen.arena.allocator().dupe(
|
|
||||||
u8,
|
|
||||||
cell.char.grapheme,
|
|
||||||
) catch |e| oom(e);
|
|
||||||
}
|
|
||||||
global.shared_screen.obj = .{
|
|
||||||
.width = screen.width,
|
|
||||||
.height = screen.height,
|
|
||||||
.width_pix = screen.width_pix,
|
|
||||||
.height_pix = screen.height_pix,
|
|
||||||
.buf = buf,
|
|
||||||
.cursor_row = screen.cursor_row,
|
|
||||||
.cursor_col = screen.cursor_col,
|
|
||||||
.cursor_vis = screen.cursor_vis,
|
|
||||||
.unicode = undefined,
|
|
||||||
.width_method = undefined,
|
|
||||||
.mouse_shape = screen.mouse_shape,
|
|
||||||
.cursor_shape = undefined,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const WindowBounds = struct {
|
const WindowBounds = struct {
|
||||||
|
@ -650,8 +487,8 @@ fn updateWindowSize(
|
||||||
bounds_ref: *?WindowBounds,
|
bounds_ref: *?WindowBounds,
|
||||||
) void {
|
) void {
|
||||||
const dpi = win32.dpiFromHwnd(hwnd);
|
const dpi = win32.dpiFromHwnd(hwnd);
|
||||||
const text_format_editor = global.text_format_editor.getOrCreate(FontCacheParams{ .dpi = dpi, .fontsize = getFontSize() });
|
const font = getFont(dpi, getFontSize(), getFontFace());
|
||||||
const cell_size = getCellSize(text_format_editor);
|
const cell_size = font.getCellSize(i32);
|
||||||
|
|
||||||
var window_rect: win32.RECT = undefined;
|
var window_rect: win32.RECT = undefined;
|
||||||
if (0 == win32.GetWindowRect(hwnd, &window_rect)) fatalWin32(
|
if (0 == win32.GetWindowRect(hwnd, &window_rect)) fatalWin32(
|
||||||
|
@ -681,35 +518,6 @@ fn updateWindowSize(
|
||||||
setWindowPosRect(hwnd, new_rect);
|
setWindowPosRect(hwnd, new_rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: we round the text metric up to the nearest integer which
|
|
||||||
// means our background rectangles will be aligned. We accomodate
|
|
||||||
// for any gap added by doing this by centering the text.
|
|
||||||
fn getCellSize(text_format: *win32.IDWriteTextFormat) XY(i32) {
|
|
||||||
var text_layout: *win32.IDWriteTextLayout = undefined;
|
|
||||||
{
|
|
||||||
const hr = global.dwrite_factory.CreateTextLayout(
|
|
||||||
win32.L("█"),
|
|
||||||
1,
|
|
||||||
text_format,
|
|
||||||
std.math.floatMax(f32),
|
|
||||||
std.math.floatMax(f32),
|
|
||||||
&text_layout,
|
|
||||||
);
|
|
||||||
if (hr < 0) fatalHr("CreateTextLayout", hr);
|
|
||||||
}
|
|
||||||
defer _ = text_layout.IUnknown.Release();
|
|
||||||
|
|
||||||
var metrics: win32.DWRITE_TEXT_METRICS = undefined;
|
|
||||||
{
|
|
||||||
const hr = text_layout.GetMetrics(&metrics);
|
|
||||||
if (hr < 0) fatalHr("GetMetrics", hr);
|
|
||||||
}
|
|
||||||
return .{
|
|
||||||
.x = @max(1, @as(i32, @intFromFloat(@floor(metrics.width)))),
|
|
||||||
.y = @max(1, @as(i32, @intFromFloat(@floor(metrics.height)))),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const CellPos = struct {
|
const CellPos = struct {
|
||||||
cell: XY(i32),
|
cell: XY(i32),
|
||||||
offset: XY(i32),
|
offset: XY(i32),
|
||||||
|
@ -757,9 +565,10 @@ fn sendMouse(
|
||||||
wparam: win32.WPARAM,
|
wparam: win32.WPARAM,
|
||||||
lparam: win32.LPARAM,
|
lparam: win32.LPARAM,
|
||||||
) void {
|
) void {
|
||||||
const point = ddui.pointFromLparam(lparam);
|
const point = win32ext.pointFromLparam(lparam);
|
||||||
const state = stateFromHwnd(hwnd);
|
const state = stateFromHwnd(hwnd);
|
||||||
const cell_size = state.currently_rendered_cell_size orelse return;
|
const dpi = win32.dpiFromHwnd(hwnd);
|
||||||
|
const cell_size = getFont(dpi, getFontSize(), getFontFace()).getCellSize(i32);
|
||||||
const cell = CellPos.init(cell_size, point.x, point.y);
|
const cell = CellPos.init(cell_size, point.x, point.y);
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
.move => {
|
.move => {
|
||||||
|
@ -807,9 +616,10 @@ fn sendMouseWheel(
|
||||||
wparam: win32.WPARAM,
|
wparam: win32.WPARAM,
|
||||||
lparam: win32.LPARAM,
|
lparam: win32.LPARAM,
|
||||||
) void {
|
) void {
|
||||||
const point = ddui.pointFromLparam(lparam);
|
const point = win32ext.pointFromLparam(lparam);
|
||||||
const state = stateFromHwnd(hwnd);
|
const state = stateFromHwnd(hwnd);
|
||||||
const cell_size = state.currently_rendered_cell_size orelse return;
|
const dpi = win32.dpiFromHwnd(hwnd);
|
||||||
|
const cell_size = getFont(dpi, getFontSize(), getFontFace()).getCellSize(i32);
|
||||||
const cell = CellPos.init(cell_size, point.x, point.y);
|
const cell = CellPos.init(cell_size, point.x, point.y);
|
||||||
// const fwKeys = win32.loword(wparam);
|
// const fwKeys = win32.loword(wparam);
|
||||||
state.scroll_delta += @as(i16, @bitCast(win32.hiword(wparam)));
|
state.scroll_delta += @as(i16, @bitCast(win32.hiword(wparam)));
|
||||||
|
@ -1159,70 +969,30 @@ fn WndProc(
|
||||||
return 0;
|
return 0;
|
||||||
},
|
},
|
||||||
win32.WM_PAINT => {
|
win32.WM_PAINT => {
|
||||||
const dpi = win32.dpiFromHwnd(hwnd);
|
|
||||||
const client_size = getClientSize(hwnd);
|
|
||||||
const state = stateFromHwnd(hwnd);
|
const state = stateFromHwnd(hwnd);
|
||||||
|
const dpi = win32.dpiFromHwnd(hwnd);
|
||||||
const err: HResultError = blk: {
|
const font = getFont(dpi, getFontSize(), getFontFace());
|
||||||
var ps: win32.PAINTSTRUCT = undefined;
|
render.paint(hwnd, &state.render_state, font, &global.screen);
|
||||||
_ = win32.BeginPaint(hwnd, &ps) orelse return fatalWin32(
|
|
||||||
"BeginPaint",
|
|
||||||
win32.GetLastError(),
|
|
||||||
);
|
|
||||||
defer if (0 == win32.EndPaint(hwnd, &ps)) fatalWin32(
|
|
||||||
"EndPaint",
|
|
||||||
win32.GetLastError(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (state.maybe_d2d == null) {
|
|
||||||
var err: HResultError = undefined;
|
|
||||||
state.maybe_d2d = D2d.init(hwnd, &err) catch break :blk err;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const size: win32.D2D_SIZE_U = .{
|
|
||||||
.width = @intCast(client_size.x),
|
|
||||||
.height = @intCast(client_size.y),
|
|
||||||
};
|
|
||||||
const hr = state.maybe_d2d.?.target.Resize(&size);
|
|
||||||
if (hr < 0) break :blk HResultError{ .context = "D2dResize", .hr = hr };
|
|
||||||
}
|
|
||||||
state.maybe_d2d.?.target.ID2D1RenderTarget.BeginDraw();
|
|
||||||
|
|
||||||
const text_format_editor = global.text_format_editor.getOrCreate(FontCacheParams{ .dpi = dpi, .fontsize = getFontSize() });
|
|
||||||
state.currently_rendered_cell_size = getCellSize(text_format_editor);
|
|
||||||
|
|
||||||
{
|
|
||||||
global.shared_screen.mutex.lock();
|
|
||||||
defer global.shared_screen.mutex.unlock();
|
|
||||||
paint(
|
|
||||||
&state.maybe_d2d.?,
|
|
||||||
RGB.from_u24(if (state.background) |b| @intCast(0xffffff & b) else 0),
|
|
||||||
&global.shared_screen.obj,
|
|
||||||
text_format_editor,
|
|
||||||
state.currently_rendered_cell_size.?,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
break :blk HResultError{
|
|
||||||
.context = "D2dEndDraw",
|
|
||||||
.hr = state.maybe_d2d.?.target.ID2D1RenderTarget.EndDraw(null, null),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
if (err.hr == win32.D2DERR_RECREATE_TARGET) {
|
|
||||||
std.log.debug("D2DERR_RECREATE_TARGET", .{});
|
|
||||||
state.maybe_d2d.?.deinit();
|
|
||||||
state.maybe_d2d = null;
|
|
||||||
win32.invalidateHwnd(hwnd);
|
|
||||||
} else if (err.hr < 0) std.debug.panic("paint error: {}", .{err});
|
|
||||||
return 0;
|
return 0;
|
||||||
},
|
},
|
||||||
win32.WM_GETDPISCALEDSIZE => {
|
win32.WM_GETDPISCALEDSIZE => {
|
||||||
const inout_size: *win32.SIZE = @ptrFromInt(@as(usize, @bitCast(lparam)));
|
const inout_size: *win32.SIZE = @ptrFromInt(@as(usize, @bitCast(lparam)));
|
||||||
const new_dpi: u32 = @intCast(0xffffffff & wparam);
|
const new_dpi: u32 = @intCast(0xffffffff & wparam);
|
||||||
const text_format_editor = global.text_format_editor.getOrCreate(FontCacheParams{ .dpi = new_dpi, .fontsize = getFontSize() });
|
// we don't want to update the font with the new dpi until after
|
||||||
const cell_size = getCellSize(text_format_editor);
|
// the dpi change is effective, so, we get the cell size from the current font/dpi
|
||||||
|
// and re-scale it based on the new dpi ourselves
|
||||||
|
const current_dpi = win32.dpiFromHwnd(hwnd);
|
||||||
|
const font = getFont(current_dpi, getFontSize(), getFontFace());
|
||||||
|
const current_cell_size_i32 = font.getCellSize(i32);
|
||||||
|
const current_cell_size: XY(f32) = .{
|
||||||
|
.x = @floatFromInt(current_cell_size_i32.x),
|
||||||
|
.y = @floatFromInt(current_cell_size_i32.y),
|
||||||
|
};
|
||||||
|
const scale: f32 = @as(f32, @floatFromInt(new_dpi)) / @as(f32, @floatFromInt(current_dpi));
|
||||||
|
const rescaled_cell_size: XY(i32) = .{
|
||||||
|
.x = @intFromFloat(@round(current_cell_size.x * scale)),
|
||||||
|
.y = @intFromFloat(@round(current_cell_size.y * scale)),
|
||||||
|
};
|
||||||
const new_rect = calcWindowRect(
|
const new_rect = calcWindowRect(
|
||||||
new_dpi,
|
new_dpi,
|
||||||
.{
|
.{
|
||||||
|
@ -1232,7 +1002,7 @@ fn WndProc(
|
||||||
.bottom = inout_size.cy,
|
.bottom = inout_size.cy,
|
||||||
},
|
},
|
||||||
win32.WMSZ_BOTTOMRIGHT,
|
win32.WMSZ_BOTTOMRIGHT,
|
||||||
cell_size,
|
rescaled_cell_size,
|
||||||
);
|
);
|
||||||
inout_size.* = .{
|
inout_size.* = .{
|
||||||
.cx = new_rect.right,
|
.cx = new_rect.right,
|
||||||
|
@ -1254,13 +1024,13 @@ fn WndProc(
|
||||||
=> {
|
=> {
|
||||||
const do_sanity_check = true;
|
const do_sanity_check = true;
|
||||||
if (do_sanity_check) {
|
if (do_sanity_check) {
|
||||||
const client_pixel_size: XY(u16) = .{
|
const lparam_size: XY(u16) = .{
|
||||||
.x = win32.loword(lparam),
|
.x = win32.loword(lparam),
|
||||||
.y = win32.hiword(lparam),
|
.y = win32.hiword(lparam),
|
||||||
};
|
};
|
||||||
const client_size = getClientSize(hwnd);
|
const client_size = getClientSize(u16, hwnd);
|
||||||
std.debug.assert(client_pixel_size.x == client_size.x);
|
std.debug.assert(lparam_size.x == client_size.x);
|
||||||
std.debug.assert(client_pixel_size.y == client_size.y);
|
std.debug.assert(lparam_size.y == client_size.y);
|
||||||
}
|
}
|
||||||
sendResize(hwnd);
|
sendResize(hwnd);
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -1281,20 +1051,6 @@ fn WndProc(
|
||||||
win32.invalidateHwnd(hwnd);
|
win32.invalidateHwnd(hwnd);
|
||||||
return 0;
|
return 0;
|
||||||
},
|
},
|
||||||
win32.WM_ERASEBKGND => {
|
|
||||||
const state = stateFromHwnd(hwnd);
|
|
||||||
if (!state.erase_bg_done) {
|
|
||||||
state.erase_bg_done = true;
|
|
||||||
const brush = win32.CreateSolidBrush(toColorRef(.{ .r = 29, .g = 29, .b = 29 })) orelse
|
|
||||||
fatalWin32("CreateSolidBrush", win32.GetLastError());
|
|
||||||
defer deleteObject(brush);
|
|
||||||
const hdc: win32.HDC = @ptrFromInt(wparam);
|
|
||||||
var rect: win32.RECT = undefined;
|
|
||||||
if (0 == win32.GetClientRect(hwnd, &rect)) @panic("");
|
|
||||||
if (0 == win32.FillRect(hdc, &rect, brush)) @panic("");
|
|
||||||
}
|
|
||||||
return 1; // background erased
|
|
||||||
},
|
|
||||||
win32.WM_CLOSE => {
|
win32.WM_CLOSE => {
|
||||||
const state = stateFromHwnd(hwnd);
|
const state = stateFromHwnd(hwnd);
|
||||||
state.pid.send(.{ "cmd", "quit" }) catch |e| onexit(e);
|
state.pid.send(.{ "cmd", "quit" }) catch |e| onexit(e);
|
||||||
|
@ -1305,8 +1061,10 @@ fn WndProc(
|
||||||
return WM_APP_EXIT_RESULT;
|
return WM_APP_EXIT_RESULT;
|
||||||
},
|
},
|
||||||
WM_APP_SET_BACKGROUND => {
|
WM_APP_SET_BACKGROUND => {
|
||||||
const state = stateFromHwnd(hwnd);
|
render.setBackground(
|
||||||
state.background = @intCast(wparam);
|
&stateFromHwnd(hwnd).render_state,
|
||||||
|
RGB.from_u24(@intCast(0xffffff & wparam)),
|
||||||
|
);
|
||||||
win32.invalidateHwnd(hwnd);
|
win32.invalidateHwnd(hwnd);
|
||||||
return WM_APP_SET_BACKGROUND_RESULT;
|
return WM_APP_SET_BACKGROUND_RESULT;
|
||||||
},
|
},
|
||||||
|
@ -1328,15 +1086,40 @@ fn WndProc(
|
||||||
},
|
},
|
||||||
WM_APP_SET_FONTFACE => {
|
WM_APP_SET_FONTFACE => {
|
||||||
const state = stateFromHwnd(hwnd);
|
const state = stateFromHwnd(hwnd);
|
||||||
var fontface: [:0]const u16 = undefined;
|
const fontface: *FontFace = @ptrFromInt(wparam);
|
||||||
fontface.ptr = @ptrFromInt(wparam);
|
global.fontface = fontface.*;
|
||||||
fontface.len = @intCast(lparam);
|
|
||||||
if (global.fontface) |old_fontface| std.heap.c_allocator.free(old_fontface);
|
|
||||||
global.fontface = fontface;
|
|
||||||
updateWindowSize(hwnd, win32.WMSZ_BOTTOMRIGHT, &state.bounds);
|
updateWindowSize(hwnd, win32.WMSZ_BOTTOMRIGHT, &state.bounds);
|
||||||
win32.invalidateHwnd(hwnd);
|
win32.invalidateHwnd(hwnd);
|
||||||
return WM_APP_SET_FONTFACE_RESULT;
|
return WM_APP_SET_FONTFACE_RESULT;
|
||||||
},
|
},
|
||||||
|
WM_APP_UPDATE_SCREEN => {
|
||||||
|
const screen: *const vaxis.Screen = @ptrFromInt(wparam);
|
||||||
|
_ = global.screen_arena.reset(.retain_capacity);
|
||||||
|
const buf = global.screen_arena.allocator().alloc(vaxis.Cell, screen.buf.len) catch |e| oom(e);
|
||||||
|
@memcpy(buf, screen.buf);
|
||||||
|
for (buf) |*cell| {
|
||||||
|
cell.char.grapheme = global.screen_arena.allocator().dupe(
|
||||||
|
u8,
|
||||||
|
cell.char.grapheme,
|
||||||
|
) catch |e| oom(e);
|
||||||
|
}
|
||||||
|
global.screen = .{
|
||||||
|
.width = screen.width,
|
||||||
|
.height = screen.height,
|
||||||
|
.width_pix = screen.width_pix,
|
||||||
|
.height_pix = screen.height_pix,
|
||||||
|
.buf = buf,
|
||||||
|
.cursor_row = screen.cursor_row,
|
||||||
|
.cursor_col = screen.cursor_col,
|
||||||
|
.cursor_vis = screen.cursor_vis,
|
||||||
|
.unicode = undefined,
|
||||||
|
.width_method = undefined,
|
||||||
|
.mouse_shape = screen.mouse_shape,
|
||||||
|
.cursor_shape = undefined,
|
||||||
|
};
|
||||||
|
win32.invalidateHwnd(hwnd);
|
||||||
|
return WM_APP_UPDATE_SCREEN_RESULT;
|
||||||
|
},
|
||||||
win32.WM_CREATE => {
|
win32.WM_CREATE => {
|
||||||
std.debug.assert(global.state == null);
|
std.debug.assert(global.state == null);
|
||||||
const create_struct: *win32.CREATESTRUCTW = @ptrFromInt(@as(usize, @bitCast(lparam)));
|
const create_struct: *win32.CREATESTRUCTW = @ptrFromInt(@as(usize, @bitCast(lparam)));
|
||||||
|
@ -1344,6 +1127,7 @@ fn WndProc(
|
||||||
global.state = .{
|
global.state = .{
|
||||||
.hwnd = hwnd,
|
.hwnd = hwnd,
|
||||||
.pid = create_args.pid,
|
.pid = create_args.pid,
|
||||||
|
.render_state = render.WindowState.init(hwnd),
|
||||||
};
|
};
|
||||||
std.debug.assert(&(global.state.?) == stateFromHwnd(hwnd));
|
std.debug.assert(&(global.state.?) == stateFromHwnd(hwnd));
|
||||||
sendResize(hwnd);
|
sendResize(hwnd);
|
||||||
|
@ -1363,32 +1147,25 @@ fn sendResize(
|
||||||
) void {
|
) void {
|
||||||
const dpi = win32.dpiFromHwnd(hwnd);
|
const dpi = win32.dpiFromHwnd(hwnd);
|
||||||
const state = stateFromHwnd(hwnd);
|
const state = stateFromHwnd(hwnd);
|
||||||
if (state.maybe_d2d == null) {
|
|
||||||
var err: HResultError = undefined;
|
const font = getFont(dpi, getFontSize(), getFontFace());
|
||||||
state.maybe_d2d = D2d.init(hwnd, &err) catch std.debug.panic(
|
const cell_size = font.getCellSize(u16);
|
||||||
"D2d.init failed with {}",
|
const client_size = getClientSize(u16, hwnd);
|
||||||
.{err},
|
const client_cell_count: XY(u16) = .{
|
||||||
);
|
.x = @intCast(@divTrunc(client_size.x, cell_size.x)),
|
||||||
}
|
.y = @intCast(@divTrunc(client_size.y, cell_size.y)),
|
||||||
const single_cell_size = getCellSize(
|
|
||||||
global.text_format_editor.getOrCreate(FontCacheParams{ .dpi = @intCast(dpi), .fontsize = getFontSize() }),
|
|
||||||
);
|
|
||||||
const client_pixel_size = getClientSize(hwnd);
|
|
||||||
const client_cell_size: XY(u16) = .{
|
|
||||||
.x = @intCast(@divTrunc(client_pixel_size.x, single_cell_size.x)),
|
|
||||||
.y = @intCast(@divTrunc(client_pixel_size.y, single_cell_size.y)),
|
|
||||||
};
|
};
|
||||||
std.log.debug(
|
std.log.debug(
|
||||||
"Resize Px={}x{} Cells={}x{}",
|
"Resize Px={}x{} Cells={}x{}",
|
||||||
.{ client_pixel_size.x, client_pixel_size.y, client_cell_size.x, client_cell_size.y },
|
.{ client_size.x, client_size.y, client_cell_count.x, client_cell_count.y },
|
||||||
);
|
);
|
||||||
state.pid.send(.{
|
state.pid.send(.{
|
||||||
"RDR",
|
"RDR",
|
||||||
"Resize",
|
"Resize",
|
||||||
client_cell_size.x,
|
client_cell_count.x,
|
||||||
client_cell_size.y,
|
client_cell_count.y,
|
||||||
client_pixel_size.x,
|
client_size.x,
|
||||||
client_pixel_size.y,
|
client_size.y,
|
||||||
}) catch @panic("pid send failed");
|
}) catch @panic("pid send failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1405,13 +1182,13 @@ fn fatalHr(what: []const u8, hresult: win32.HRESULT) noreturn {
|
||||||
fn deleteObject(obj: ?win32.HGDIOBJ) void {
|
fn deleteObject(obj: ?win32.HGDIOBJ) void {
|
||||||
if (0 == win32.DeleteObject(obj)) fatalWin32("DeleteObject", win32.GetLastError());
|
if (0 == win32.DeleteObject(obj)) fatalWin32("DeleteObject", win32.GetLastError());
|
||||||
}
|
}
|
||||||
fn getClientSize(hwnd: win32.HWND) XY(i32) {
|
fn getClientSize(comptime T: type, hwnd: win32.HWND) XY(T) {
|
||||||
var rect: win32.RECT = undefined;
|
var rect: win32.RECT = undefined;
|
||||||
if (0 == win32.GetClientRect(hwnd, &rect))
|
if (0 == win32.GetClientRect(hwnd, &rect))
|
||||||
fatalWin32("GetClientRect", win32.GetLastError());
|
fatalWin32("GetClientRect", win32.GetLastError());
|
||||||
std.debug.assert(rect.left == 0);
|
std.debug.assert(rect.left == 0);
|
||||||
std.debug.assert(rect.top == 0);
|
std.debug.assert(rect.top == 0);
|
||||||
return .{ .x = rect.right, .y = rect.bottom };
|
return .{ .x = @intCast(rect.right), .y = @intCast(rect.bottom) };
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calcWindowRect(
|
fn calcWindowRect(
|
||||||
|
@ -1495,6 +1272,15 @@ fn getClientInset(dpi: u32) XY(i32) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rectIntFromSize(args: struct { left: i32, top: i32, width: i32, height: i32 }) win32.RECT {
|
||||||
|
return .{
|
||||||
|
.left = args.left,
|
||||||
|
.top = args.top,
|
||||||
|
.right = args.left + args.width,
|
||||||
|
.bottom = args.top + args.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn setWindowPosRect(hwnd: win32.HWND, rect: win32.RECT) void {
|
fn setWindowPosRect(hwnd: win32.HWND, rect: win32.RECT) void {
|
||||||
if (0 == win32.SetWindowPos(
|
if (0 == win32.SetWindowPos(
|
||||||
hwnd,
|
hwnd,
|
||||||
|
@ -1506,50 +1292,3 @@ fn setWindowPosRect(hwnd: win32.HWND, rect: win32.RECT) void {
|
||||||
.{ .NOZORDER = 1 },
|
.{ .NOZORDER = 1 },
|
||||||
)) fatalWin32("SetWindowPos", win32.GetLastError());
|
)) fatalWin32("SetWindowPos", win32.GetLastError());
|
||||||
}
|
}
|
||||||
|
|
||||||
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 xterm_colors: [256]u24 = .{
|
|
||||||
0x000000, 0x800000, 0x008000, 0x808000, 0x000080, 0x800080, 0x008080, 0xc0c0c0,
|
|
||||||
0x808080, 0xff0000, 0x00ff00, 0xffff00, 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff,
|
|
||||||
|
|
||||||
0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f,
|
|
||||||
0x005f87, 0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f, 0x008787, 0x0087af,
|
|
||||||
0x0087d7, 0x0087ff, 0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff,
|
|
||||||
0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f,
|
|
||||||
0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff, 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af,
|
|
||||||
0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff,
|
|
||||||
0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, 0x5faf00, 0x5faf5f,
|
|
||||||
0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af,
|
|
||||||
0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff,
|
|
||||||
0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f,
|
|
||||||
0x875f87, 0x875faf, 0x875fd7, 0x875fff, 0x878700, 0x87875f, 0x878787, 0x8787af,
|
|
||||||
0x8787d7, 0x8787ff, 0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff,
|
|
||||||
0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f,
|
|
||||||
0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af,
|
|
||||||
0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff,
|
|
||||||
0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f,
|
|
||||||
0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700, 0xafd75f, 0xafd787, 0xafd7af,
|
|
||||||
0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff,
|
|
||||||
0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f,
|
|
||||||
0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af,
|
|
||||||
0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff,
|
|
||||||
0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff, 0xd7ff00, 0xd7ff5f,
|
|
||||||
0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, 0xff0000, 0xff005f, 0xff0087, 0xff00af,
|
|
||||||
0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff,
|
|
||||||
0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f,
|
|
||||||
0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, 0xffd700, 0xffd75f, 0xffd787, 0xffd7af,
|
|
||||||
0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff,
|
|
||||||
|
|
||||||
0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e,
|
|
||||||
0x585858, 0x606060, 0x666666, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e,
|
|
||||||
0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee,
|
|
||||||
};
|
|
||||||
|
|
84
src/win32/terminal.hlsl
Normal file
84
src/win32/terminal.hlsl
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
cbuffer GridConfig : register(b0)
|
||||||
|
{
|
||||||
|
uint2 cell_size;
|
||||||
|
uint col_count;
|
||||||
|
uint row_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 * col_count + grid_pos.x;
|
||||||
|
|
||||||
|
const uint DEBUG_MODE_NONE = 0;
|
||||||
|
const uint DEBUG_MODE_CHECKERBOARD = 1;
|
||||||
|
const uint DEBUG_MODE_GLYPH_TEXTURE = 2;
|
||||||
|
|
||||||
|
const uint DEBUG_MODE = DEBUG_MODE_NONE;
|
||||||
|
//const uint DEBUG_MODE = DEBUG_MODE_CHECKERBOARD;
|
||||||
|
//const uint DEBUG_MODE = DEBUG_MODE_GLYPH_TEXTURE;
|
||||||
|
|
||||||
|
if (DEBUG_MODE == DEBUG_MODE_CHECKERBOARD) {
|
||||||
|
uint cell_count = col_count * row_count;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Cell cell = cells[index];
|
||||||
|
float4 bg_color = UnpackRgba(cell.background);
|
||||||
|
float4 fg_color = UnpackRgba(cell.foreground);
|
||||||
|
|
||||||
|
if (DEBUG_MODE == DEBUG_MODE_GLYPH_TEXTURE) {
|
||||||
|
float4 glyph_texel = glyph_texture.Load(int3(sv_pos.xy, 0));
|
||||||
|
return lerp(bg_color, fg_color, glyph_texel);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint2 cell_pixel = uint2(sv_pos.xy) % cell_size;
|
||||||
|
|
||||||
|
uint texture_width, texture_height;
|
||||||
|
glyph_texture.GetDimensions(texture_width, texture_height);
|
||||||
|
uint2 texture_size = uint2(texture_width, texture_height);
|
||||||
|
uint cells_per_row = texture_width / cell_size.x;
|
||||||
|
|
||||||
|
uint2 glyph_cell_pos = uint2(
|
||||||
|
cell.glyph_index % cells_per_row,
|
||||||
|
cell.glyph_index / cells_per_row
|
||||||
|
);
|
||||||
|
uint2 texture_coord = glyph_cell_pos * cell_size + cell_pixel;
|
||||||
|
float4 glyph_texel = glyph_texture.Load(int3(texture_coord, 0));
|
||||||
|
return lerp(bg_color, fg_color, glyph_texel.a);
|
||||||
|
}
|
32
src/win32/win32ext.zig
Normal file
32
src/win32/win32ext.zig
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const win32 = @import("win32").everything;
|
||||||
|
|
||||||
|
// todo: these should be available in zigwin32
|
||||||
|
fn xFromLparam(lparam: win32.LPARAM) i16 {
|
||||||
|
return @bitCast(win32.loword(lparam));
|
||||||
|
}
|
||||||
|
fn yFromLparam(lparam: win32.LPARAM) i16 {
|
||||||
|
return @bitCast(win32.hiword(lparam));
|
||||||
|
}
|
||||||
|
pub fn pointFromLparam(lparam: win32.LPARAM) win32.POINT {
|
||||||
|
return win32.POINT{ .x = xFromLparam(lparam), .y = yFromLparam(lparam) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: update zigwin32 with a way to get the corresponding IID for any COM interface
|
||||||
|
pub 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;
|
||||||
|
}
|
36
src/win32/xterm.zig
Normal file
36
src/win32/xterm.zig
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
pub const colors: [256]u24 = .{
|
||||||
|
0x000000, 0x800000, 0x008000, 0x808000, 0x000080, 0x800080, 0x008080, 0xc0c0c0,
|
||||||
|
0x808080, 0xff0000, 0x00ff00, 0xffff00, 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff,
|
||||||
|
|
||||||
|
0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f,
|
||||||
|
0x005f87, 0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f, 0x008787, 0x0087af,
|
||||||
|
0x0087d7, 0x0087ff, 0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff,
|
||||||
|
0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f,
|
||||||
|
0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff, 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af,
|
||||||
|
0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff,
|
||||||
|
0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, 0x5faf00, 0x5faf5f,
|
||||||
|
0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af,
|
||||||
|
0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff,
|
||||||
|
0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f,
|
||||||
|
0x875f87, 0x875faf, 0x875fd7, 0x875fff, 0x878700, 0x87875f, 0x878787, 0x8787af,
|
||||||
|
0x8787d7, 0x8787ff, 0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff,
|
||||||
|
0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f,
|
||||||
|
0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af,
|
||||||
|
0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff,
|
||||||
|
0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f,
|
||||||
|
0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700, 0xafd75f, 0xafd787, 0xafd7af,
|
||||||
|
0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff,
|
||||||
|
0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f,
|
||||||
|
0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af,
|
||||||
|
0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff,
|
||||||
|
0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff, 0xd7ff00, 0xd7ff5f,
|
||||||
|
0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, 0xff0000, 0xff005f, 0xff0087, 0xff00af,
|
||||||
|
0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff,
|
||||||
|
0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f,
|
||||||
|
0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, 0xffd700, 0xffd75f, 0xffd787, 0xffd7af,
|
||||||
|
0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff,
|
||||||
|
|
||||||
|
0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e,
|
||||||
|
0x585858, 0x606060, 0x666666, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e,
|
||||||
|
0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee,
|
||||||
|
};
|
14
src/win32/xy.zig
Normal file
14
src/win32/xy.zig
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue