From 42bef672067071d01d4799c7cdaad358feee72b6 Mon Sep 17 00:00:00 2001 From: matthew Date: Sat, 12 Jul 2025 13:22:23 +1000 Subject: [PATCH] triangle drawn --- dub.json | 3 +- src/gears/main.d | 47 +---- src/gears/renderer.d | 111 +++++++++++- src/gears/vulkan.d | 362 ++++++++++++++++++++++++++++++++++++- src/gears/vulkan_logging.d | 2 +- src/shared/assets.d | 60 +++--- 6 files changed, 510 insertions(+), 75 deletions(-) diff --git a/dub.json b/dub.json index 94b1396..3f9d297 100644 --- a/dub.json +++ b/dub.json @@ -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"], }, diff --git a/src/gears/main.d b/src/gears/main.d index 4b30387..f9d8d6b 100644 --- a/src/gears/main.d +++ b/src/gears/main.d @@ -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); + } } diff --git a/src/gears/renderer.d b/src/gears/renderer.d index 1c9a3f8..98f5454 100644 --- a/src/gears/renderer.d +++ b/src/gears/renderer.d @@ -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); +} diff --git a/src/gears/vulkan.d b/src/gears/vulkan.d index aeb3adc..1e153f7 100644 --- a/src/gears/vulkan.d +++ b/src/gears/vulkan.d @@ -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) { diff --git a/src/gears/vulkan_logging.d b/src/gears/vulkan_logging.d index 47c2fb3..ac02998 100644 --- a/src/gears/vulkan_logging.d +++ b/src/gears/vulkan_logging.d @@ -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, diff --git a/src/shared/assets.d b/src/shared/assets.d index 87af0d8..b3c9c1e 100644 --- a/src/shared/assets.d +++ b/src/shared/assets.d @@ -61,41 +61,49 @@ struct AssetInfo AssetType type; } -bool +bool Asset_Pack_Opened = false; + +void OpenAssetPack() { - bool success = true; - string file_path = isFile("build/assets.sgp") ? "build/assets.sgp" : "assets.sgp"; - - // TODO: replace this with something that doesn't throw an exception and figure out if this is the best way to handle thing (probably isnt) - try + if (!Asset_Pack_Opened) { - Asset_File = File(file_path, "rb"); + bool success = true; + string file_path = isFile("build/assets.sgp") ? "build/assets.sgp" : "assets.sgp"; + + // TODO: replace this with something that doesn't throw an exception and figure out if this is the best way to handle thing (probably isnt) + try + { + Asset_File = File(file_path, "rb"); + } + catch (ErrnoException e) + { + Logf("OpenAssetPack failure: Unable to open file %s", file_path); + assert(false, "Unable to open asset pack file"); + } + + FileHeader[1] header_arr; + + Asset_File.rawRead(header_arr); + + Asset_Header = header_arr[0]; + + assert(Asset_Header.file_version == FILE_VERSION, "OpenAssetPack failure: file version incorrect"); + + Asset_File.seek(Asset_Header.asset_info_offset); + + Asset_File.rawRead(Asset_Info); } - catch (ErrnoException e) - { - success = false; - Logf("OpenAssetPack failure: Unable to open file %s", file_path); - } - - FileHeader[1] header_arr; - - Asset_File.rawRead(header_arr); - - Asset_Header = header_arr[0]; - - assert(Asset_Header.file_version == FILE_VERSION, "OpenAssetPack failure: file version incorrect"); - - 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;