triangle drawn

This commit is contained in:
matthew 2025-07-12 13:22:23 +10:00
parent 9c5ad29f78
commit 42bef67206
6 changed files with 510 additions and 75 deletions

View File

@ -13,7 +13,7 @@
"sourcePaths": ["src/gears", "src/shared", "src/generated", "external/xxhash"],
"libs-linux": ["xcb", "X11", "X11-xcb", "vulkan", "stdc++"],
"libs-windows": [],
"preGenerateCommands-linux": ["./build-vma.sh", "build/Codegen", "dub main:packer", "build/Packer"],
"preGenerateCommands-linux": ["./build-vma.sh", "build/Codegen", "dub main:packer"],
"preGenerateCommands-windows": [],
"dflags-dmd": ["-P=-DSTBI_NO_SIMD"]
},
@ -26,6 +26,7 @@
"sourcePaths": ["src/packer", "src/shared", "src/generated", "external/xxhash"],
"sourceFiles-linux": ["build/libstb_image.a", "build/libm3d.a"],
"preGenerateCommands-linux": ["./build-vma.sh"],
"postGenerateCommands-linux": ["build/Packer"],
"preGenerateCommands-windows": [],
"dflags-dmd": ["-P=-DSTBI_NO_SIMD"],
},

View File

@ -2,54 +2,19 @@ public import includes;
import std.stdio;
import aliases;
import core.memory;
import u = util : Result;
import p = platform;
import a = alloc;
import vk = vulkan : Vulkan;
import ap = assets;
import r = renderer;
void main()
{
p.Window window = p.CreateWindow("Video Game", 1920, 1080);
assert(ap.OpenAssetPack(), "OpenAssetPack failure");
r.Renderer rd = r.Init(&window);
scope(exit) r.Destroy(&rd);
Result!(Vulkan) result = vk.Init(&window, u.MB(24), u.MB(32));
Vulkan vulkan = result.value;
u8[] vert_bytes = ap.LoadAssetData("shaders/triangle.vert");
u8[] frag_bytes = ap.LoadAssetData("shaders/triangle.frag");
assert(vert_bytes != null && frag_bytes != null, "Unable to load shader data");
auto vert_module = vk.BuildShader(&vulkan, vert_bytes);
auto frag_module = vk.BuildShader(&vulkan, frag_bytes);
assert(vert_module.ok && frag_module.ok, "Unable to build vulkan shaders");
r.GfxPipelineInfo pipeline_info = {
vertex_shader: vert_module.value,
frag_shader: frag_module.value,
};
auto pipeline = vk.CreateGraphicsPipeline(&vulkan, &pipeline_info);
u8[] comp_bytes = ap.LoadAssetData("shaders/gradient.comp");
assert(comp_bytes != null, "Unable to load compute shader data");
auto comp_module = vk.BuildShader(&vulkan, comp_bytes);
assert(comp_module.ok, "Unable to build compute shader");
auto comp_pipeline = vk.CreateComputePipeline(&vulkan, comp_module.value);
vk.Destroy(&vulkan, comp_module.value);
vk.Destroy(&vulkan, comp_pipeline);
vk.Destroy(&vulkan, vert_module.value);
vk.Destroy(&vulkan, frag_module.value);
vk.Destroy(&vulkan, pipeline);
vk.Destroy(&vulkan);
while (true)
{
r.Cycle(&rd);
}
}

View File

@ -1,8 +1,14 @@
import aliases;
import includes;
import assets;
import util;
import ap = assets;
import vk = vulkan;
import u = util;
import p = platform;
alias Shader = VkShaderModule;
alias Pipeline = VkPipeline;
alias Pipeline = vk.PipelineHandle;
alias Attribute = VkVertexInputAttributeDescription;
enum InputRate : int
@ -20,6 +26,14 @@ enum Format: int
RGBA_F32 = VK_FORMAT_R32G32B32A32_SFLOAT,
}
enum PipelineType : int
{
Graphics,
Compute,
}
alias PT = PipelineType;
struct GfxPipelineInfo
{
Shader vertex_shader;
@ -29,4 +43,99 @@ struct GfxPipelineInfo
Attribute[] vertex_attributes;
}
struct Renderer
{
vk.Vulkan vulkan;
p.Window* window;
Pipeline triangle_pipeline;
Pipeline compute_pipeline;
}
Renderer
Init(p.Window* window)
{
u.Result!(vk.Vulkan) vk_result = vk.Init(window, u.MB(24), u.MB(32));
assert(vk_result.ok, "Init failure: Unable to initialize Vulkan");
Renderer rd = {
vulkan: vk_result.value,
window: window,
};
rd.triangle_pipeline = BuildGfxPipeline(&rd, "shaders/triangle.vert", "shaders/triangle.frag");
rd.compute_pipeline = BuildCompPipeline(&rd, "shaders/gradient.comp");
return rd;
}
bool
Cycle(Renderer* rd)
{
bool success = vk.BeginFrame(&rd.vulkan);
if (success)
{
vk.Bind(&rd.vulkan, rd.triangle_pipeline);
vk.Draw(&rd.vulkan, 3, 1);
success = vk.FinishFrame(&rd.vulkan);
}
return success;
}
Pipeline
BuildGfxPipeline(Renderer* rd, string vertex, string fragment)
{
u8[] vert_bytes = ap.LoadAssetData(vertex);
u8[] frag_bytes = ap.LoadAssetData(fragment);
scope(exit)
{
ap.UnloadAssetData(vertex);
ap.UnloadAssetData(fragment);
}
assert(vert_bytes && frag_bytes, "Unable to load shaders");
Result!(Shader) vert_module = vk.BuildShader(&rd.vulkan, vert_bytes);
Result!(Shader) frag_module = vk.BuildShader(&rd.vulkan, frag_bytes);
assert(vert_module.ok && frag_module.ok, "Unable to build vulkan shaders");
scope(exit)
{
vk.Destroy(&rd.vulkan, vert_module.value);
vk.Destroy(&rd.vulkan, frag_module.value);
}
GfxPipelineInfo pipeline_info = {
vertex_shader: vert_module.value,
frag_shader: frag_module.value,
};
return vk.CreateGraphicsPipeline(&rd.vulkan, &pipeline_info);
}
Pipeline
BuildCompPipeline(Renderer* rd, string compute)
{
u8[] comp_bytes = ap.LoadAssetData(compute);
assert(comp_bytes != null, "Unable to load compute shader data");
scope(exit) ap.UnloadAssetData(compute);
Result!(Shader) comp_module = vk.BuildShader(&rd.vulkan, comp_bytes);
assert(comp_module.ok, "Unable to build compute shader");
scope(exit) vk.Destroy(&rd.vulkan, comp_module.value);
return vk.CreateComputePipeline(&rd.vulkan, comp_module.value);
}
void
Destroy(Renderer* rd)
{
vk.Destroy(&rd.vulkan, rd.triangle_pipeline);
vk.Destroy(&rd.vulkan, rd.compute_pipeline);
vk.Destroy(&rd.vulkan);
}

View File

@ -119,6 +119,8 @@ struct Vulkan
a.Arena arena;
a.Arena[FRAME_OVERLAP] frame_arenas;
u64 frame_no;
u.SLList!(SI) cleanup_list;
p.Window* window;
@ -136,6 +138,8 @@ struct Vulkan
VkExtent3D swapchain_extent;
ImageView[] present_images;
u32 image_index;
ImageView draw_image;
ImageView depth_image;
@ -162,6 +166,12 @@ struct Vulkan
QueueInfo queues;
}
struct PipelineHandle
{
VkPipeline handle;
VkPipelineBindPoint type;
}
struct QueueInfo
{
i32 gfx_index, tfer_index;
@ -211,6 +221,338 @@ Init(p.Window* window, u64 permanent_mem, u64 frame_mem)
return result;
}
u64
FrameIndex(Vulkan* vk)
{
return vk.frame_no % FRAME_OVERLAP;
}
u64
SemIndex(Vulkan* vk)
{
return vk.frame_no % vk.present_images.length;
}
VkImage
CurrentImage(Vulkan* vk)
{
return vk.present_images[vk.image_index].base.image;
}
bool
BeginFrame(Vulkan* vk)
{
u64 index = FrameIndex(vk);
u64 sem_index = SemIndex(vk);
// TODO: move vkWaitForFences so it no longer holds up the frame, will need to change how fences are handled in regards to images though
VkResult result = vkWaitForFences(vk.device, 1, vk.render_fences.ptr + index, VK_TRUE, 1000000000);
bool success = VkCheck("BeginFrame failure: vkWaitForFences error", result);
if (success)
{
result = vkResetFences(vk.device, 1, vk.render_fences.ptr + index);
success = VkCheck("BeginFrame failure: vkResetFences error", result);
}
if (success)
{
result = vkAcquireNextImageKHR(vk.device, vk.swapchain, 1000000000, vk.swapchain_sems[sem_index], null, &vk.image_index);
if (result == VK_ERROR_OUT_OF_DATE_KHR)
{
RecreateSwapchain(vk);
}
else
{
success = VkCheck("BeginFrame failure: vkAcquireNextImageKHR error", result);
}
}
if (success)
{
result = vkResetCommandBuffer(vk.cmds[index], 0);
success = VkCheck("BeginFrame failure: vkResetCommandBuffer failure", result);
}
if (success)
{
VkCommandBufferBeginInfo cmd_info = {
sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
flags: VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
};
result = vkBeginCommandBuffer(vk.cmds[index], &cmd_info);
success = VkCheck("BeginFrame failure: vkBeginCommandBuffer error", result);
}
if (success)
{
Transition(vk.cmds[index], &vk.draw_image.base, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
Transition(vk.cmds[index], &vk.depth_image.base, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
VkImage image = CurrentImage(vk);
Transition(vk.cmds[index], image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
f32[4] clear_col;
clear_col[] = 0.2;
VkRenderingAttachmentInfo col_attach = {
sType: VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
imageView: vk.draw_image.view,
imageLayout: vk.draw_image.base.layout,
loadOp: VK_ATTACHMENT_LOAD_OP_CLEAR,
storeOp: VK_ATTACHMENT_STORE_OP_STORE,
clearValue: { color: { clear_col } },
};
VkRenderingAttachmentInfo depth_attach = {
sType: VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
imageView: vk.depth_image.view,
imageLayout: vk.depth_image.base.layout,
loadOp: VK_ATTACHMENT_LOAD_OP_CLEAR,
storeOp: VK_ATTACHMENT_STORE_OP_STORE,
};
VkRenderingInfo render_info = {
sType: VK_STRUCTURE_TYPE_RENDERING_INFO,
layerCount: 1,
colorAttachmentCount: 1,
pColorAttachments: &col_attach,
pDepthAttachment: &depth_attach,
renderArea: {
extent: {
width: vk.swapchain_extent.width,
height: vk.swapchain_extent.height,
},
},
};
vkCmdBeginRendering(vk.cmds[index], &render_info);
vkCmdBindDescriptorSets(
vk.cmds[index],
VK_PIPELINE_BIND_POINT_GRAPHICS,
vk.pipeline_layout,
0,
cast(u32)vk.desc_sets.length,
vk.desc_sets.ptr,
0,
null
);
}
return success;
}
bool
FinishFrame(Vulkan* vk)
{
scope(exit) vk.frame_no += 1;
bool success = true;
u64 index = FrameIndex(vk);
u64 sem_index = SemIndex(vk);
VkImage image = CurrentImage(vk);
vkCmdEndRendering(vk.cmds[index]);
Transition(vk.cmds[index], &vk.draw_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
VkExtent2D extent = {
width: vk.swapchain_extent.width,
height: vk.swapchain_extent.height,
};
// TODO: Find out how to copy from same dimension images
Copy(vk.cmds[index], vk.draw_image.base.image, image, extent, extent);
Transition(vk.cmds[index], image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
VkResult result = vkEndCommandBuffer(vk.cmds[index]);
success = VkCheck("FinishFrame failure: vkEndCommandBuffer error", result);
if (success)
{
VkCommandBufferSubmitInfo cmd_info = {
sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO,
commandBuffer: vk.cmds[index],
};
VkSemaphoreSubmitInfo wait_info = {
sType: VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
semaphore: vk.swapchain_sems[sem_index],
stageMask: VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
value: 1,
};
VkSemaphoreSubmitInfo signal_info = {
sType: VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
semaphore: vk.render_sems[index],
stageMask: VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT,
value: 1,
};
VkSubmitInfo2 submit_info = {
sType: VK_STRUCTURE_TYPE_SUBMIT_INFO_2,
waitSemaphoreInfoCount: 1,
pWaitSemaphoreInfos: &wait_info,
signalSemaphoreInfoCount: 1,
pSignalSemaphoreInfos: &signal_info,
commandBufferInfoCount: 1,
pCommandBufferInfos: &cmd_info,
};
result = vkQueueSubmit2(vk.queues.gfx_queue, 1, &submit_info, vk.render_fences[index]);
success = VkCheck("FinishFrame failure: vkQueueSubmit2 error", result);
}
if (success)
{
VkPresentInfoKHR present_info = {
sType: VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
swapchainCount: 1,
pSwapchains: &vk.swapchain,
waitSemaphoreCount: 1,
pWaitSemaphores: vk.render_sems.ptr + index,
pImageIndices: &vk.image_index,
};
result = vkQueuePresentKHR(vk.queues.gfx_queue, &present_info);
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR)
{
RecreateSwapchain(vk);
}
else
{
success = VkCheck("FinishFrame failure: vkQueuePresentKHR failure", result);
}
}
return success;
}
void
Draw(Vulkan* vk, u32 index_count, u32 instance_count)
{
vkCmdDraw(vk.cmds[FrameIndex(vk)], index_count, instance_count, 0, 0);
}
void
DrawIndexed(Vulkan* vk, u32 index_count, u32 instance_count)
{
vkCmdDrawIndexed(vk.cmds[FrameIndex(vk)], index_count, instance_count, 0, 0, 0);
}
pragma(inline): void
Copy(VkCommandBuffer cmd, VkImage src, VkImage dst, VkExtent2D src_ext, VkExtent2D dst_ext)
{
VkImageBlit2 blit = {
sType: VK_STRUCTURE_TYPE_IMAGE_BLIT_2,
srcOffsets: [
{},
{ x: cast(i32)src_ext.width, y: cast(i32)src_ext.height, z: 1 },
],
dstOffsets: [
{},
{ x: cast(i32)dst_ext.width, y: cast(i32)dst_ext.height, z: 1 },
],
srcSubresource: {
aspectMask: VK_IMAGE_ASPECT_COLOR_BIT,
baseArrayLayer: 0,
layerCount: 1,
mipLevel: 0,
},
dstSubresource: {
aspectMask: VK_IMAGE_ASPECT_COLOR_BIT,
baseArrayLayer: 0,
layerCount: 1,
mipLevel: 0,
},
};
VkBlitImageInfo2 blit_info = {
sType: VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2,
srcImage: src,
srcImageLayout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
dstImage: dst,
dstImageLayout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
filter: VK_FILTER_LINEAR,
regionCount: 1,
pRegions: &blit,
};
vkCmdBlitImage2(cmd, &blit_info);
}
void
Bind(Vulkan* vk, PipelineHandle pipeline)
{
u64 index = FrameIndex(vk);
vkCmdBindPipeline(vk.cmds[index], pipeline.type, pipeline.handle);
VkViewport viewport = {
width: cast(f32)vk.swapchain_extent.width,
height: cast(f32)vk.swapchain_extent.height,
maxDepth: 1.0F,
};
vkCmdSetViewport(vk.cmds[index], 0, 1, &viewport);
VkRect2D scissor = {
extent: {
width: vk.swapchain_extent.width,
height: vk.swapchain_extent.height,
},
};
vkCmdSetScissor(vk.cmds[index], 0, 1, &scissor);
}
pragma(inline): void
Transition(VkCommandBuffer cmd, VkImage image, VkImageLayout current_layout, VkImageLayout new_layout)
{
VkImageMemoryBarrier2 barrier = {
sType: VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
srcStageMask: VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
srcAccessMask: VK_ACCESS_2_MEMORY_WRITE_BIT,
dstStageMask: VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
dstAccessMask: VK_ACCESS_2_MEMORY_WRITE_BIT | VK_ACCESS_2_MEMORY_READ_BIT,
oldLayout: current_layout,
newLayout: new_layout,
image: image,
subresourceRange: {
aspectMask: new_layout == VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT,
baseMipLevel: 0,
levelCount: VK_REMAINING_MIP_LEVELS,
baseArrayLayer: 0,
layerCount: VK_REMAINING_ARRAY_LAYERS,
},
};
VkDependencyInfo dep_info = {
sType: VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
imageMemoryBarrierCount: 1,
pImageMemoryBarriers: &barrier,
};
vkCmdPipelineBarrier2(cmd, &dep_info);
}
pragma(inline): void
Transition(VkCommandBuffer cmd, ImageView* view, VkImageLayout new_layout)
{
Transition(cmd, view.base.image, view.base.layout, new_layout);
view.base.layout = new_layout;
}
pragma(inline): void
Transition(VkCommandBuffer cmd, Image* image, VkImageLayout new_layout)
{
Transition(cmd, image.image, image.layout, new_layout);
image.layout = new_layout;
}
Result!(Shader)
BuildShader(Vulkan* vk, u8[] bytes)
{
@ -343,9 +685,9 @@ CreateGraphicsPipeline(Vulkan* vk, GfxPipelineInfo* build_info)
layout: vk.pipeline_layout,
};
VkPipeline pipeline;
PipelineHandle pipeline = { type: VK_PIPELINE_BIND_POINT_GRAPHICS };
VkResult result = vkCreateGraphicsPipelines(vk.device, null, 1, &create_info, null, &pipeline);
VkResult result = vkCreateGraphicsPipelines(vk.device, null, 1, &create_info, null, &pipeline.handle);
assert(VkCheck("CreateGraphicsPipeline failure", result), "Unable to build pipeline");
return pipeline;
@ -367,8 +709,8 @@ CreateComputePipeline(Vulkan* vk, Shader shader)
__traits(getMember, &info.stage, "module") = shader;
VkPipeline pipeline;
VkResult result = vkCreateComputePipelines(vk.device, null, 1, &info, null, &pipeline);
PipelineHandle pipeline = { type: VK_PIPELINE_BIND_POINT_COMPUTE };
VkResult result = vkCreateComputePipelines(vk.device, null, 1, &info, null, &pipeline.handle);
assert(VkCheck("CreateComputePipeline failure", result), "Unable to build pipeline");
return pipeline;
@ -383,7 +725,7 @@ Destroy(Vulkan* vk, Shader shader)
void
Destroy(Vulkan* vk, Pipeline pipeline)
{
vkDestroyPipeline(vk.device, pipeline, null);
vkDestroyPipeline(vk.device, pipeline.handle, null);
}
void
@ -971,6 +1313,16 @@ CreateSwapchain(Vulkan* vk)
return success;
}
void
RecreateSwapchain(Vulkan* vk)
{
vkDeviceWaitIdle(vk.device);
Destroy(vk.swapchain, vk.present_images, vk.device);
CreateSwapchain(vk);
}
bool
InitVMA(Vulkan* vk)
{

View File

@ -5,7 +5,7 @@ import std.conv;
import std.string;
import core.stdc.string : strlen;
VkBool32
extern(System) VkBool32
DebugCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
VkDebugUtilsMessageTypeFlagsEXT message_type,

View File

@ -61,8 +61,12 @@ struct AssetInfo
AssetType type;
}
bool
bool Asset_Pack_Opened = false;
void
OpenAssetPack()
{
if (!Asset_Pack_Opened)
{
bool success = true;
string file_path = isFile("build/assets.sgp") ? "build/assets.sgp" : "assets.sgp";
@ -74,8 +78,8 @@ OpenAssetPack()
}
catch (ErrnoException e)
{
success = false;
Logf("OpenAssetPack failure: Unable to open file %s", file_path);
assert(false, "Unable to open asset pack file");
}
FileHeader[1] header_arr;
@ -89,13 +93,17 @@ OpenAssetPack()
Asset_File.seek(Asset_Header.asset_info_offset);
Asset_File.rawRead(Asset_Info);
return success;
}
}
u8[]
LoadAssetData(string name)
{
if (!Asset_Pack_Opened)
{
OpenAssetPack();
}
u64 hash = Hash(name);
u8[] data = null;