From 32f6e6c14da7eb8766329569d22a4691aea4502d Mon Sep 17 00:00:00 2001 From: WayfinderAK Date: Thu, 14 May 2026 14:22:07 -0800 Subject: [PATCH] Initial commit --- .gitignore | 9 + .pi/agents/algorithm-researcher.md | 33 ++ .pi/agents/optimization-reviewer.md | 32 ++ .pi/agents/research-and-review.chain.md | 34 ++ .pi/agents/zig-learning-mentor.md | 26 ++ .pi/settings.json | 10 + AGENTS.md | 28 ++ README.md | 30 ++ build.zig | 71 +++ build.zig.zon | 20 + docs/learning-roadmap.md | 57 +++ docs/research-guidelines.md | 27 ++ shaders/square.frag | 7 + shaders/square.vert | 7 + src/main.zig | 563 ++++++++++++++++++++++++ 15 files changed, 954 insertions(+) create mode 100644 .gitignore create mode 100644 .pi/agents/algorithm-researcher.md create mode 100644 .pi/agents/optimization-reviewer.md create mode 100644 .pi/agents/research-and-review.chain.md create mode 100644 .pi/agents/zig-learning-mentor.md create mode 100644 .pi/settings.json create mode 100644 AGENTS.md create mode 100644 README.md create mode 100644 build.zig create mode 100644 build.zig.zon create mode 100644 docs/learning-roadmap.md create mode 100644 docs/research-guidelines.md create mode 100644 shaders/square.frag create mode 100644 shaders/square.vert create mode 100644 src/main.zig diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..21fd07f --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.zig-cache/ +zig-out/ +zig-pkg/ +.DS_Store +*.log +.env +.env.* +!.env.example +.pi/npm/ diff --git a/.pi/agents/algorithm-researcher.md b/.pi/agents/algorithm-researcher.md new file mode 100644 index 0000000..52e8bae --- /dev/null +++ b/.pi/agents/algorithm-researcher.md @@ -0,0 +1,33 @@ +--- +name: algorithm-researcher +description: Research chess algorithms, board representation, move generation, search, graphics, and Zig/library behavior with strict citations +tools: read, bash, web_search, code_search, fetch_content, get_search_content +thinking: high +--- +You are the Algorithm Researcher for zig-chess. + +Mission: +- Help the owner learn algorithms for chess rules, board representation, move generation, engine/search experiments, and board rendering. +- Do not write implementation code unless explicitly requested. Prefer equations, pseudocode, data-flow descriptions, and learning exercises. +- Be highly factual and cite sources for every nontrivial claim. + +Research standards: +- Use web_search for current docs, papers, and reputable references when needed. +- Prefer primary sources: Zig language docs, Vulkan/GLFW docs, chess rules/standards, engine documentation, textbooks, seminal papers, lecture notes from universities, and well-known technical references. +- For each recommendation, include references with title, author/organization, URL/DOI if available, and why it is relevant. +- Separate: established result, implementation tradeoff, hypothesis, and suggested experiment. +- If sources disagree or are incomplete, say so clearly. + +Focus areas: +- Chess rules: legal move generation, check/checkmate/stalemate, castling, en passant, promotion, repetition and draw rules as needed. +- Board representation: mailbox arrays, 0x88, bitboards, piece lists, state snapshots. +- Testing: perft, FEN fixtures, deterministic regression tests. +- Search/evaluation: minimax, negamax, alpha-beta pruning, move ordering, transposition tables, iterative deepening. +- Visualization: 2D board rendering, coordinate transforms, highlighting, UI state. + +Output format: +1. Short answer / recommendation. +2. Concepts and algorithm description. +3. Tradeoffs and failure modes. +4. Suggested experiments or benchmarks. +5. References. diff --git a/.pi/agents/optimization-reviewer.md b/.pi/agents/optimization-reviewer.md new file mode 100644 index 0000000..3abb179 --- /dev/null +++ b/.pi/agents/optimization-reviewer.md @@ -0,0 +1,32 @@ +--- +name: optimization-reviewer +description: Review designs and future Zig code for performance, memory layout, profiling, SIMD/bitboards, rendering latency, and correctness risks with references +tools: read, bash, web_search, code_search, fetch_content, get_search_content +thinking: high +--- +You are the Optimization Reviewer for zig-chess. + +Mission: +- Help the owner understand and improve performance without taking away the learning process. +- Do not write implementation code unless explicitly requested. Provide review notes, measurement plans, pseudocode, and targeted explanations. +- Ground performance advice in measurable claims and cite references. + +Review standards: +- Ask for or propose benchmarks before recommending micro-optimizations. +- Distinguish latency, throughput, memory bandwidth, allocation behavior, correctness, determinism, and UI responsiveness. +- Cite Zig documentation, compiler/LLVM documentation, CPU/vendor optimization manuals, Vulkan/GLFW documentation, and reliable performance engineering resources. +- Call out assumptions about CPU architecture, cache sizes, SIMD availability, GPU/driver behavior, OS/windowing backend, and compiler flags. + +Focus areas: +- Zig allocators, slices, error handling costs, comptime, packed/extern structs, vector types, build modes. +- Data-oriented design: AoS vs SoA, cache locality, branch prediction, alignment. +- Chess performance: board representations, bitboards, move generation, perft, search tree branching, transposition tables. +- Rendering performance: frame pacing, CPU/GPU boundaries, command-buffer/resource lifetime, avoiding unnecessary redraw work. +- Benchmark quality: deterministic positions, timing methodology, regression tracking. + +Output format: +1. Performance risk summary. +2. Measurement plan and metrics. +3. Optimization options ranked by expected impact/risk. +4. Zig-specific considerations. +5. References. diff --git a/.pi/agents/research-and-review.chain.md b/.pi/agents/research-and-review.chain.md new file mode 100644 index 0000000..4194908 --- /dev/null +++ b/.pi/agents/research-and-review.chain.md @@ -0,0 +1,34 @@ +--- +name: research-and-review +description: Research an algorithm, then review performance and learning implications +--- + +## algorithm-researcher +output: algorithm-research.md +progress: true + +Research this topic for zig-chess: {task} + +Return a factual, cited algorithm explanation, tradeoffs, and suggested learning experiments. Do not write implementation code. + +## optimization-reviewer +reads: algorithm-research.md +output: optimization-review.md +progress: true + +Review the researched approach from a Zig performance, correctness, and rendering/chess-engine perspective. + +Use the prior research as input: {previous} + +Return measurement plans, optimization risks, and cited references. Do not write implementation code. + +## zig-learning-mentor +reads: algorithm-research.md, optimization-review.md +output: learning-plan.md +progress: true + +Turn the research and optimization review into a learning plan for an experienced Go/Python/C/Rust programmer learning Zig. + +Use prior context: {previous} + +Return exercises and study notes. Do not write implementation code. diff --git a/.pi/agents/zig-learning-mentor.md b/.pi/agents/zig-learning-mentor.md new file mode 100644 index 0000000..6ac6701 --- /dev/null +++ b/.pi/agents/zig-learning-mentor.md @@ -0,0 +1,26 @@ +--- +name: zig-learning-mentor +description: Explain Zig concepts for an experienced Go/Python/C/Rust programmer while preserving learning ownership +tools: read, bash, web_search, code_search, fetch_content, get_search_content +thinking: medium +--- +You are the Zig Learning Mentor for zig-chess. + +Mission: +- Teach Zig to an experienced programmer who knows Go, Python, some C, and Rust. +- Do not write implementation code unless explicitly requested. +- Prefer conceptual explanations, small exercises, review prompts, and references to official docs. + +Teaching style: +- Use comparisons to Go, Python, C, and Rust when they clarify the idea. +- Focus on deep topics: allocators, ownership-by-convention, error unions, comptime, build system, C interop, vectors/SIMD, memory layout, testing, profiling. +- Use chess examples when helpful: board arrays, move structs, tagged unions for piece types, bitsets/bitboards, legal-move tests, and deterministic benchmarks. +- Ask guiding questions when a design choice would be more educational than receiving a direct answer. +- If code is requested, keep it minimal and explain every Zig-specific construct. + +Output format: +1. Concept explanation. +2. Analogy to known languages. +3. Pitfalls and debugging tips. +4. Suggested exercise. +5. References. diff --git a/.pi/settings.json b/.pi/settings.json new file mode 100644 index 0000000..66b5ac6 --- /dev/null +++ b/.pi/settings.json @@ -0,0 +1,10 @@ +{ + "packages": [ + "npm:pi-subagents", + "npm:pi-web-access", + "npm:@earendil-works/pi-agent-core@0.74.0", + "npm:@earendil-works/pi-ai@0.74.0", + "npm:@earendil-works/pi-coding-agent@0.74.0", + "npm:@earendil-works/pi-tui@0.74.0" + ] +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..f97831e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,28 @@ +# AGENTS.md + +## Core collaboration rule + +The project owner is learning Zig. Do **not** write implementation code unless the owner explicitly asks for code. Prefer explanations, pseudocode, diagrams in prose, performance reasoning, references, and review comments. + +## Required research standard + +For factual claims about algorithms, chess rules/standards, numerical methods, graphics, Zig behavior, compiler behavior, or performance: + +- Cite primary documentation, papers, books, standards, or reputable technical references where possible. +- Distinguish established facts from hypotheses or implementation choices. +- Include enough citation detail for follow-up: title, author/organization, URL or DOI when available, and the specific topic/section if useful. +- Prefer current upstream documentation for Zig and library/API behavior. +- Call out uncertainty and suggest verification experiments or benchmarks. + +## Learning focus + +- Explain Zig concepts in terms of Go/Python/C/Rust analogies when helpful. +- Favor small learning tasks and experiments over finished solutions. +- When reviewing designs, describe tradeoffs and what evidence would decide between options. +- Keep notes and plans in `docs/` unless asked otherwise. + +## Safety and quality + +- Do not add dependencies casually; explain the learning or technical reason first. +- Do not introduce opaque frameworks that hide the Zig concepts being studied. +- For chess/graphics work, discuss correctness, testability, determinism, latency, profiling, and data-layout tradeoffs. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f01ad14 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# zig-chess + +A learning-first Zig project for building chess software while continuing the current Vulkan/GLFW rendering work. + +## Intent + +This repository keeps the current rendering bootstrap, but pivots the long-term learning target to chess. The near-term goal is to get the basic rendering path working; after that, chess rules, board representation, UI, and engine/search work become the main Zig learning vehicle. + +The assistant should support learning with explanations, references, algorithms, tradeoffs, and review feedback; it should not write implementation code unless explicitly asked. + +## Initial milestones + +1. Finish the minimal Vulkan/GLFW rendering bootstrap. +2. Render a chessboard with simple 2D primitives. +3. Model board coordinates, pieces, moves, turns, castling, en passant, and promotion. +4. Add legal move generation and rule validation. +5. Build an interactive local UI: select pieces, highlight legal moves, update board state. +6. Add chess notation and data helpers: FEN, algebraic notation basics, and PGN later if useful. +7. Explore engine concepts: evaluation, minimax/negamax, alpha-beta pruning, move ordering, and perft tests. +8. Performance work: board representation tradeoffs, allocation behavior, profiling, and benchmarks. + +## Project-local Pi agents + +This repo includes project agents in `.pi/agents/` and Pi package settings in `.pi/settings.json`: + +- `algorithm-researcher` — factual research on chess algorithms, board representation, move generation, search, graphics, and Zig/library behavior. +- `optimization-reviewer` — performance, memory-layout, profiling, SIMD/bitboard, and Zig-specific optimization review. +- `zig-learning-mentor` — learning-focused Zig explanations and exercises without writing code for you. + +The agents are configured for web-enabled research via `pi-web-access` tools (`web_search`, `fetch_content`, `get_search_content`) and subagent orchestration via `pi-subagents`. diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..ec6912f --- /dev/null +++ b/build.zig @@ -0,0 +1,71 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const exe = b.addExecutable(.{ + .name = "zig-chess", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + .link_libc = true, + }), + }); + + const vulkan_headers = b.dependency("vulkan_headers", .{}); + + const vulkan = b.dependency("vulkan", .{ + .registry = vulkan_headers.path("registry/vk.xml"), + }).module("vulkan-zig"); + + exe.root_module.addImport("vulkan", vulkan); + + const zglfw = b.dependency("zglfw", .{ + .target = target, + .optimize = optimize, + .import_vulkan = true, + }); + + const zglfw_mod = zglfw.module("root"); + zglfw_mod.addImport("vulkan", vulkan); + + exe.root_module.addImport("zglfw", zglfw_mod); + + if (target.result.os.tag != .emscripten) { + exe.root_module.linkLibrary(zglfw.artifact("glfw")); + } + + const vert_cmd = b.addSystemCommand(&.{ + "glslc", + "--target-env=vulkan1.2", + "-o", + }); + const vert_spv = vert_cmd.addOutputFileArg("square.vert.spv"); + vert_cmd.addFileArg(b.path("shaders/square.vert")); + + const frag_cmd = b.addSystemCommand(&.{ + "glslc", + "--target-env=vulkan1.2", + "-o", + }); + const frag_spv = frag_cmd.addOutputFileArg("square.frag.spv"); + frag_cmd.addFileArg(b.path("shaders/square.frag")); + + exe.root_module.addAnonymousImport("square_vertex_shader", .{ + .root_source_file = vert_spv, + }); + + exe.root_module.addAnonymousImport("square_fragment_shader", .{ + .root_source_file = frag_spv, + }); + + b.installArtifact(exe); + + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + + const run_step = b.step("run", "Run zig-chess"); + run_step.dependOn(&run_cmd.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..1da12cc --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,20 @@ +.{ + .name = .zig_chess, + .version = "0.16.0", + .dependencies = .{ + .zglfw = .{ + .url = "git+https://github.com/zig-gamedev/zglfw#51003c105d23db378bb59ce415a387b22f1b0892", + .hash = "zglfw-0.10.0-dev-zgVDNIy4IQDJNRy4jrP1As-SZxfJpuWhU1iJ-wBab_VD", + }, + .vulkan = .{ + .url = "git+https://github.com/Snektron/vulkan-zig#b496a6a561ffbbeb530b0f9ed4e059f88c0723a5", + .hash = "vulkan-0.0.0-r7Ytx7N9AwD7IZt5_XNHtkJ4G9qY0pCH-cpcOsbL8wzD", + }, + .vulkan_headers = .{ + .url = "https://github.com/KhronosGroup/Vulkan-Headers/archive/v1.3.283.tar.gz", + .hash = "N-V-__8AAAkkoQGn5z1yoNVrwqZfnYmZp8AZ5CJgoHRMQI0c", + }, + }, + .paths = .{""}, + .fingerprint = 0x5cebff35895787ea, +} diff --git a/docs/learning-roadmap.md b/docs/learning-roadmap.md new file mode 100644 index 0000000..33f4afe --- /dev/null +++ b/docs/learning-roadmap.md @@ -0,0 +1,57 @@ +# Learning roadmap + +## Phase 0: Zig environment and language model + +- Install/verify Zig and editor tooling. +- Learn `zig build`, tests, allocators, slices, error unions, `defer`, `comptime`, and C interop. +- Keep notes on concepts that differ from Go/Rust/C. + +## Phase 1: Rendering bootstrap + +Goal: finish the copied Vulkan/GLFW path well enough to draw and present simple 2D shapes. + +Learning targets: +- Vulkan setup order and resource lifetimes. +- Swapchain, render pass, framebuffers, command buffers, synchronization. +- Coordinate transforms for a 2D board. +- Frame timing and redraw strategies. + +## Phase 2: Chess model + +Learning targets: +- Board coordinates and piece representation. +- Move structs and game-state snapshots. +- Rule edge cases: castling, en passant, promotion, check, checkmate, stalemate. +- Tests for correctness before UI complexity. + +## Phase 3: Interactive board UI + +Learning targets: +- Mapping mouse/window coordinates to squares. +- Selection state and legal-move highlighting. +- Separating rendering state from game rules. +- Debug overlays for learning and testing. + +## Phase 4: Notation and validation + +Learning targets: +- FEN import/export. +- Algebraic notation basics. +- Perft-style move-generation tests. +- Golden tests and small fixtures. + +## Phase 5: Engine/search experiments + +Learning targets: +- Evaluation functions. +- Minimax/negamax and alpha-beta pruning. +- Move ordering, transposition tables, and iterative deepening as later topics. +- Deterministic benchmarks for search changes. + +## Phase 6: Optimization + +Learning targets: +- Profiling before optimizing. +- Board representation options: mailbox arrays, 0x88, bitboards. +- Allocation-free move generation experiments. +- SIMD/vectorization and cache-aware data layouts where evidence supports it. diff --git a/docs/research-guidelines.md b/docs/research-guidelines.md new file mode 100644 index 0000000..06334e6 --- /dev/null +++ b/docs/research-guidelines.md @@ -0,0 +1,27 @@ +# Research guidelines + +Use these standards when asking subagents for chess algorithms, rendering, or optimization help. + +## Good prompts + +- "Compare mailbox arrays, 0x88, and bitboards for a beginner-friendly Zig chess engine; cite sources and propose experiments." +- "Research legal move generation edge cases and perft testing for chess correctness." +- "Review a simple Vulkan chessboard renderer for resource lifetime and data-layout risks." +- "Explain alpha-beta pruning and move ordering with references and benchmark ideas." + +## Expected evidence + +- Official Zig documentation for Zig behavior. +- Chess programming references, engine documentation, or reputable technical articles for chess algorithms. +- Vulkan/GLFW upstream documentation for rendering behavior. +- Vendor/compiler documentation for low-level optimization claims. + +## Required distinction + +Every research answer should separate: + +1. Established facts. +2. Design tradeoffs. +3. Hypotheses requiring measurement. +4. Suggested experiments/benchmarks. +5. References for follow-up. diff --git a/shaders/square.frag b/shaders/square.frag new file mode 100644 index 0000000..e093b78 --- /dev/null +++ b/shaders/square.frag @@ -0,0 +1,7 @@ +#version 450 + +layout(location = 0) out vec4 out_color; + +void main() { + out_color = vec4(0.1, 0.8, 1.0, 1.0); +} diff --git a/shaders/square.vert b/shaders/square.vert new file mode 100644 index 0000000..cd5443a --- /dev/null +++ b/shaders/square.vert @@ -0,0 +1,7 @@ +#version 450 + +layout(location = 0) in vec2 in_position; + +void main() { + gl_Position = vec4(in_position, 0.0, 1.0); +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..e593cfc --- /dev/null +++ b/src/main.zig @@ -0,0 +1,563 @@ +const std = @import("std"); +const glfw = @import("zglfw"); +const vk = @import("vulkan"); + +const square_vert_spv = @embedFile("square_vertex_shader"); +const square_frag_spv = @embedFile("square_fragment_shader"); + +pub fn main() !void { + std.debug.print("zig-chess bootstrap\n", .{}); + std.debug.print("vertex shader bytes: {}\n", .{square_vert_spv.len}); + std.debug.print("fragment shader bytes: {}\n", .{square_frag_spv.len}); + + try glfw.init(); + defer glfw.terminate(); + + glfw.windowHint(.client_api, .no_api); + const window = try glfw.Window.create( + 800, + 600, + "zig-chess", + null, + null, + ); + defer window.destroy(); + + std.debug.print("GLFW platform: {any}\n", .{glfw.getPlatform()}); + std.debug.print("Vulkan supported by GLFW: {}\n", .{glfw.isVulkanSupported()}); + + const size = window.getSize(); + const fb_size = window.getFramebufferSize(); + + std.debug.print("window size: {}x{}\n", .{ size[0], size[1] }); + std.debug.print("framebuffer size: {}x{}\n", .{ fb_size[0], fb_size[1] }); + std.debug.print("window visible attr: {}\n", .{window.getAttribute(.visible)}); + + window.show(); + window.requestAttention(); + + const base = vk.BaseWrapper.load(glfw.getInstanceProcAddress); + const required_extensions = try glfw.getRequiredInstanceExtensions(); + + const app_info = vk.ApplicationInfo{ + .p_application_name = "zig-chess", + .application_version = 1, + .p_engine_name = "zig-chess", + .engine_version = 1, + .api_version = @bitCast(vk.makeApiVersion(0, 1, 2, 0)), + }; + + const instance_create_info = vk.InstanceCreateInfo{ + .p_application_info = &app_info, + .enabled_extension_count = @intCast(required_extensions.len), + .pp_enabled_extension_names = required_extensions.ptr, + }; + + const instance = try base.createInstance(&instance_create_info, null); + + std.debug.print("required instance extensions:\n", .{}); + for (required_extensions) |extension| { + std.debug.print(" {s}\n", .{extension}); + } + + std.debug.print("Created Vulkan Instance\n", .{}); + + const vki = vk.InstanceWrapper.load(instance, base.dispatch.vkGetInstanceProcAddr.?); + defer vki.destroyInstance(instance, null); + + var surface: vk.SurfaceKHR = undefined; + try glfw.createWindowSurface(instance, window, null, &surface); + defer vki.destroySurfaceKHR(instance, surface, null); + std.debug.print("Created Vulkan surface\n", .{}); + + const physical_devices = try vki.enumeratePhysicalDevicesAlloc(instance, std.heap.page_allocator); + defer std.heap.page_allocator.free(physical_devices); + + std.debug.print("physical devices: {}\n", .{physical_devices.len}); + + for (physical_devices, 0..) |physical_device, i| { + const props = vki.getPhysicalDeviceProperties(physical_device); + std.debug.print("device {}: {s}\n", .{ i, std.mem.sliceTo(&props.device_name, 0) }); + const queue_families = try vki.getPhysicalDeviceQueueFamilyPropertiesAlloc( + physical_device, + std.heap.page_allocator, + ); + defer std.heap.page_allocator.free(queue_families); + + for (queue_families, 0..) |queue_family, queue_index| { + const supports_graphics = queue_family.queue_flags.graphics_bit; + const supports_compute = queue_family.queue_flags.compute_bit; + const supports_transfer = queue_family.queue_flags.transfer_bit; + + const supports_present = try vki.getPhysicalDeviceSurfaceSupportKHR( + physical_device, + @intCast(queue_index), + surface, + ); + + std.debug.print( + " queue {}: count={}, graphics={}, compute={}, transfer={}, present={}\n", + .{ + queue_index, + queue_family.queue_count, + supports_graphics, + supports_compute, + supports_transfer, + supports_present, + }, + ); + } + } + + const selected_physical_device = physical_devices[1]; + const graphics_queue_family_index: u32 = 0; + + const selected_props = vki.getPhysicalDeviceProperties(selected_physical_device); + std.debug.print( + "selected device: {s}, queue family {}\n", + .{ + std.mem.sliceTo(&selected_props.device_name, 0), + graphics_queue_family_index, + }, + ); + + const queue_priority: f32 = 1.0; + + const queue_create_info = vk.DeviceQueueCreateInfo{ + .queue_family_index = graphics_queue_family_index, + .queue_count = 1, + .p_queue_priorities = @ptrCast(&queue_priority), + }; + + const device_extensions = [_][*:0]const u8{ + "VK_KHR_swapchain", + }; + + const device_create_info = vk.DeviceCreateInfo{ + .queue_create_info_count = 1, + .p_queue_create_infos = @ptrCast(&queue_create_info), + .enabled_extension_count = device_extensions.len, + .pp_enabled_extension_names = &device_extensions, + }; + + const device = try vki.createDevice(selected_physical_device, &device_create_info, null); + std.debug.print("created logical device\n", .{}); + + const vkd = vk.DeviceWrapper.load(device, vki.dispatch.vkGetDeviceProcAddr.?); + defer vkd.destroyDevice(device, null); + + const graphics_queue = vkd.getDeviceQueue(device, graphics_queue_family_index, 0); + + std.debug.print("retrieved graphics queue\n", .{}); + + const surface_caps = try vki.getPhysicalDeviceSurfaceCapabilitiesKHR( + selected_physical_device, + surface, + ); + + std.debug.print( + "surface current extent: {}x{}\n", + .{ + surface_caps.current_extent.width, + surface_caps.current_extent.height, + }, + ); + + std.debug.print( + "surface min/max image count: {}/{}\n", + .{ + surface_caps.min_image_count, + surface_caps.max_image_count, + }, + ); + + const surface_formats = try vki.getPhysicalDeviceSurfaceFormatsAllocKHR( + selected_physical_device, + surface, + std.heap.page_allocator, + ); + defer std.heap.page_allocator.free(surface_formats); + + std.debug.print("surface formats: {}\n", .{surface_formats.len}); + for (surface_formats, 0..) |format, i| { + std.debug.print( + " format {}: format={any}, color_space={any}\n", + .{ i, format.format, format.color_space }, + ); + } + + const present_modes = try vki.getPhysicalDeviceSurfacePresentModesAllocKHR( + selected_physical_device, + surface, + std.heap.page_allocator, + ); + defer std.heap.page_allocator.free(present_modes); + + std.debug.print("present modes: {}\n", .{present_modes.len}); + for (present_modes, 0..) |mode, i| { + std.debug.print(" present mode {}: {any}\n", .{ i, mode }); + } + + var chosen_surface_format = surface_formats[0]; + for (surface_formats) |format| { + if (format.format == .b8g8r8a8_srgb and + format.color_space == .srgb_nonlinear_khr) + { + chosen_surface_format = format; + break; + } + } + + var chosen_present_mode: vk.PresentModeKHR = .fifo_khr; + for (present_modes) |mode| { + if (mode == .fifo_khr) { + chosen_present_mode = mode; + break; + } + } + + const framebuffer_size = window.getFramebufferSize(); + + const chosen_extent = if (surface_caps.current_extent.width != std.math.maxInt(u32)) + surface_caps.current_extent + else + vk.Extent2D{ + .width = @intCast(framebuffer_size[0]), + .height = @intCast(framebuffer_size[1]), + }; + + var chosen_image_count = surface_caps.min_image_count + 1; + if (surface_caps.max_image_count != 0 and chosen_image_count > surface_caps.max_image_count) { + chosen_image_count = surface_caps.max_image_count; + } + + std.debug.print( + "chosen swapchain format={any}, color_space={any}\n", + .{ chosen_surface_format.format, chosen_surface_format.color_space }, + ); + std.debug.print("chosen present mode={any}\n", .{chosen_present_mode}); + std.debug.print( + "chosen extent={}x{}\n", + .{ chosen_extent.width, chosen_extent.height }, + ); + std.debug.print("chosen image count={}\n", .{chosen_image_count}); + + const swapchain_create_info = vk.SwapchainCreateInfoKHR{ + .surface = surface, + .min_image_count = chosen_image_count, + .image_format = chosen_surface_format.format, + .image_color_space = chosen_surface_format.color_space, + .image_extent = chosen_extent, + .image_array_layers = 1, + .image_usage = .{ + .color_attachment_bit = true, + }, + .image_sharing_mode = .exclusive, + .pre_transform = surface_caps.current_transform, + .composite_alpha = .{ + .opaque_bit_khr = true, + }, + .present_mode = chosen_present_mode, + .clipped = .true, + }; + + const swapchain = try vkd.createSwapchainKHR(device, &swapchain_create_info, null); + defer vkd.destroySwapchainKHR(device, swapchain, null); + + std.debug.print("created swapchain\n", .{}); + + const swapchain_images = try vkd.getSwapchainImagesAllocKHR( + device, + swapchain, + std.heap.page_allocator, + ); + defer std.heap.page_allocator.free(swapchain_images); + + std.debug.print("swapchain images: {}\n", .{swapchain_images.len}); + + const swapchain_image_views = try std.heap.page_allocator.alloc( + vk.ImageView, + swapchain_images.len, + ); + defer std.heap.page_allocator.free(swapchain_image_views); + + for (swapchain_images, 0..) |image, i| { + const image_view_create_info = vk.ImageViewCreateInfo{ + .image = image, + .view_type = .@"2d", + .format = chosen_surface_format.format, + .components = .{ + .r = .identity, + .g = .identity, + .b = .identity, + .a = .identity, + }, + .subresource_range = .{ + .aspect_mask = .{ + .color_bit = true, + }, + .base_mip_level = 0, + .level_count = 1, + .base_array_layer = 0, + .layer_count = 1, + }, + }; + + swapchain_image_views[i] = try vkd.createImageView( + device, + &image_view_create_info, + null, + ); + } + + defer { + for (swapchain_image_views) |image_view| { + vkd.destroyImageView(device, image_view, null); + } + } + + std.debug.print("created swapchain image views: {}\n", .{swapchain_image_views.len}); + + const color_attachment = vk.AttachmentDescription{ + .format = chosen_surface_format.format, + .samples = .{ .@"1_bit" = true }, + .load_op = .clear, + .store_op = .store, + .stencil_load_op = .dont_care, + .stencil_store_op = .dont_care, + .initial_layout = .undefined, + .final_layout = .present_src_khr, + }; + + const color_attachment_ref = vk.AttachmentReference{ + .attachment = 0, + .layout = .color_attachment_optimal, + }; + + const subpass = vk.SubpassDescription{ + .pipeline_bind_point = .graphics, + .color_attachment_count = 1, + .p_color_attachments = @ptrCast(&color_attachment_ref), + }; + + const subpass_dependency = vk.SubpassDependency{ + .src_subpass = vk.SUBPASS_EXTERNAL, + .dst_subpass = 0, + .src_stage_mask = .{ + .color_attachment_output_bit = true, + }, + .src_access_mask = .{}, + .dst_stage_mask = .{ + .color_attachment_output_bit = true, + }, + .dst_access_mask = .{ + .color_attachment_write_bit = true, + }, + }; + + const render_pass_create_info = vk.RenderPassCreateInfo{ + .attachment_count = 1, + .p_attachments = @ptrCast(&color_attachment), + .subpass_count = 1, + .p_subpasses = @ptrCast(&subpass), + .dependency_count = 1, + .p_dependencies = @ptrCast(&subpass_dependency), + }; + + const render_pass = try vkd.createRenderPass(device, &render_pass_create_info, null); + defer vkd.destroyRenderPass(device, render_pass, null); + + std.debug.print("created render pass\n", .{}); + + const framebuffers = try std.heap.page_allocator.alloc( + vk.Framebuffer, + swapchain_image_views.len, + ); + defer std.heap.page_allocator.free(framebuffers); + + for (swapchain_image_views, 0..) |image_view, i| { + const attachments = [_]vk.ImageView{image_view}; + + const framebuffer_create_info = vk.FramebufferCreateInfo{ + .render_pass = render_pass, + .attachment_count = attachments.len, + .p_attachments = &attachments, + .width = chosen_extent.width, + .height = chosen_extent.height, + .layers = 1, + }; + + framebuffers[i] = try vkd.createFramebuffer( + device, + &framebuffer_create_info, + null, + ); + } + + defer { + for (framebuffers) |framebuffer| { + vkd.destroyFramebuffer(device, framebuffer, null); + } + } + + std.debug.print("created framebuffers: {}\n", .{framebuffers.len}); + + const command_pool_create_info = vk.CommandPoolCreateInfo{ + .flags = .{ + .reset_command_buffer_bit = true, + }, + .queue_family_index = graphics_queue_family_index, + }; + + const command_pool = try vkd.createCommandPool( + device, + &command_pool_create_info, + null, + ); + defer vkd.destroyCommandPool(device, command_pool, null); + + std.debug.print("created command pool\n", .{}); + + const command_buffers = try std.heap.page_allocator.alloc( + vk.CommandBuffer, + framebuffers.len, + ); + defer std.heap.page_allocator.free(command_buffers); + + const command_buffer_allocate_info = vk.CommandBufferAllocateInfo{ + .command_pool = command_pool, + .level = .primary, + .command_buffer_count = @intCast(command_buffers.len), + }; + + try vkd.allocateCommandBuffers( + device, + &command_buffer_allocate_info, + command_buffers.ptr, + ); + + std.debug.print("allocated command buffers: {}\n", .{command_buffers.len}); + + for (command_buffers, 0..) |command_buffer, i| { + const begin_info = vk.CommandBufferBeginInfo{}; + + try vkd.beginCommandBuffer(command_buffer, &begin_info); + + const clear_color = vk.ClearValue{ + .color = .{ + .float_32 = .{ 0.02, 0.02, 0.08, 1.0 }, + }, + }; + + const render_pass_begin_info = vk.RenderPassBeginInfo{ + .render_pass = render_pass, + .framebuffer = framebuffers[i], + .render_area = .{ + .offset = .{ .x = 0, .y = 0 }, + .extent = chosen_extent, + }, + .clear_value_count = 1, + .p_clear_values = @ptrCast(&clear_color), + }; + + vkd.cmdBeginRenderPass( + command_buffer, + &render_pass_begin_info, + .@"inline", + ); + + vkd.cmdEndRenderPass(command_buffer); + + try vkd.endCommandBuffer(command_buffer); + } + + std.debug.print("recorded command buffers\n", .{}); + + const semaphore_create_info = vk.SemaphoreCreateInfo{}; + + const image_available_semaphore = try vkd.createSemaphore( + device, + &semaphore_create_info, + null, + ); + defer vkd.destroySemaphore(device, image_available_semaphore, null); + + const render_finished_semaphore = try vkd.createSemaphore( + device, + &semaphore_create_info, + null, + ); + defer vkd.destroySemaphore(device, render_finished_semaphore, null); + + const fence_create_info = vk.FenceCreateInfo{ + .flags = .{ + .signaled_bit = true, + }, + }; + + const in_flight_fence = try vkd.createFence( + device, + &fence_create_info, + null, + ); + defer vkd.destroyFence(device, in_flight_fence, null); + + std.debug.print("created synchronization objects\n", .{}); + + const wait_fences = [_]vk.Fence{in_flight_fence}; + _ = try vkd.waitForFences(device, &wait_fences, .true, std.math.maxInt(u64)); + try vkd.resetFences(device, &wait_fences); + + const acquire_result = try vkd.acquireNextImageKHR( + device, + swapchain, + std.math.maxInt(u64), + image_available_semaphore, + .null_handle, + ); + + const image_index = acquire_result.image_index; + std.debug.print("acquired swapchain image: {}\n", .{image_index}); + + const wait_semaphores = [_]vk.Semaphore{image_available_semaphore}; + const wait_stages = [_]vk.PipelineStageFlags{ + .{ + .color_attachment_output_bit = true, + }, + }; + const signal_semaphores = [_]vk.Semaphore{render_finished_semaphore}; + const submit_command_buffers = [_]vk.CommandBuffer{ + command_buffers[image_index], + }; + + const submit_info = vk.SubmitInfo{ + .wait_semaphore_count = wait_semaphores.len, + .p_wait_semaphores = &wait_semaphores, + .p_wait_dst_stage_mask = &wait_stages, + .command_buffer_count = submit_command_buffers.len, + .p_command_buffers = &submit_command_buffers, + .signal_semaphore_count = signal_semaphores.len, + .p_signal_semaphores = &signal_semaphores, + }; + + try vkd.queueSubmit(graphics_queue, &[_]vk.SubmitInfo{submit_info}, in_flight_fence); + + const present_swapchains = [_]vk.SwapchainKHR{swapchain}; + const present_image_indices = [_]u32{image_index}; + + const present_info = vk.PresentInfoKHR{ + .wait_semaphore_count = signal_semaphores.len, + .p_wait_semaphores = &signal_semaphores, + .swapchain_count = present_swapchains.len, + .p_swapchains = &present_swapchains, + .p_image_indices = &present_image_indices, + }; + + _ = try vkd.queuePresentKHR(graphics_queue, &present_info); + + std.debug.print("presented one frame\n", .{}); + + while (!window.shouldClose()) { + glfw.pollEvents(); + } +}