Initial commit

This commit is contained in:
WayfinderAK 2026-05-14 14:22:07 -08:00
commit 32f6e6c14d
No known key found for this signature in database
15 changed files with 954 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
.zig-cache/
zig-out/
zig-pkg/
.DS_Store
*.log
.env
.env.*
!.env.example
.pi/npm/

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

10
.pi/settings.json Normal file
View File

@ -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"
]
}

28
AGENTS.md Normal file
View File

@ -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.

30
README.md Normal file
View File

@ -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`.

71
build.zig Normal file
View File

@ -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);
}

20
build.zig.zon Normal file
View File

@ -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,
}

57
docs/learning-roadmap.md Normal file
View File

@ -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.

View File

@ -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.

7
shaders/square.frag Normal file
View File

@ -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);
}

7
shaders/square.vert Normal file
View File

@ -0,0 +1,7 @@
#version 450
layout(location = 0) in vec2 in_position;
void main() {
gl_Position = vec4(in_position, 0.0, 1.0);
}

563
src/main.zig Normal file
View File

@ -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();
}
}