render map objects: decode compiled W3DMesh art, skin and draw over terrain
Read the buildings and props a map places from its compiled art (the uncompressed worldbuilder stream + embedded DDS textures): - ra3.models: BAB asset-stream parser (lazy slices), W3DMesh + D3DHierarchy decode, DDS (DXT1/3/5 + uncompressed) decode, per-mesh bone remap and bind-pose single-joint skinning, flattened to one world-space triangle soup. - ra3.map: parse the ObjectsList chunk into (type, x, y, z, angle). - ra3.terrain: render3d rasterises the scene over the raymarched terrain with a z-buffer; gpu_terrain carries the scene for the GPU backends. - ra3.vulkan: second pipeline + depth attachment, terrain.frag writes gl_FragDepth, and a small depth bias keeps ground decals from z-fighting. - objects.fx shaders (compiled to SPIR-V), embedded like scene/terrain. Only opaque parts are drawn: FX-light billboards (DefaultW3D.fx / BasicW3D.fx) and damage-fill shells (BuildingsGenericDamageFill.fx) are skipped, since the latter paint the wrecked interior (e.g. orange CBBuilding_Wood) over the shell. Ground-decal meshes with no diffuse role and the Road templates themselves are still not drawn.
This commit is contained in:
+373
-17
@@ -92,6 +92,7 @@ export namespace ra3::vulkan {
|
||||
if (!this->create_device()) return false;
|
||||
if (!this->create_swapchain()) return false;
|
||||
if (!this->create_render_pass()) return false;
|
||||
if (!this->create_framebuffers()) return false;
|
||||
if (!this->create_pipeline()) return false;
|
||||
if (!this->create_commands()) return false;
|
||||
if (!this->create_sync()) return false;
|
||||
@@ -341,6 +342,12 @@ export namespace ra3::vulkan {
|
||||
if (!this->create_terrain_pipeline()) return false;
|
||||
if (!this->create_terrain_images(terrain)) return false;
|
||||
if (!this->create_terrain_descriptors()) return false;
|
||||
if (!terrain.objects.empty()) {
|
||||
if (!this->create_object_pipeline()) return false;
|
||||
if (!this->create_object_buffers(terrain)) return false;
|
||||
if (!this->create_object_descriptors()) return false;
|
||||
objects_ready_ = true;
|
||||
}
|
||||
terrain_ready_ = true;
|
||||
}
|
||||
if (overlay.label_changed) this->update_overlay(overlay_label_, overlay_label_set_, overlay_label_w_, overlay_label_h_, overlay.label);
|
||||
@@ -669,6 +676,11 @@ export namespace ra3::vulkan {
|
||||
VkPipelineMultisampleStateCreateInfo multisample{};
|
||||
multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
|
||||
multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
||||
VkPipelineDepthStencilStateCreateInfo depth_stencil{};
|
||||
depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
|
||||
depth_stencil.depthTestEnable = VK_TRUE;
|
||||
depth_stencil.depthWriteEnable = VK_TRUE;
|
||||
depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
|
||||
VkPipelineColorBlendAttachmentState blend_attachment{};
|
||||
blend_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
||||
VkPipelineColorBlendStateCreateInfo blend{};
|
||||
@@ -690,6 +702,7 @@ export namespace ra3::vulkan {
|
||||
pipeline_info.pViewportState = &viewport;
|
||||
pipeline_info.pRasterizationState = &raster;
|
||||
pipeline_info.pMultisampleState = &multisample;
|
||||
pipeline_info.pDepthStencilState = &depth_stencil;
|
||||
pipeline_info.pColorBlendState = &blend;
|
||||
pipeline_info.pDynamicState = &dynamic;
|
||||
pipeline_info.layout = terrain_pipeline_layout_;
|
||||
@@ -737,6 +750,197 @@ export namespace ra3::vulkan {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Host-visible vertex/index buffer, filled once (the map's geometry is static). */
|
||||
[[nodiscard]] auto make_host_buffer(const void *data, VkDeviceSize bytes, VkBufferUsageFlags usage, VkBuffer &buffer,
|
||||
VkDeviceMemory &memory) -> bool {
|
||||
VkBufferCreateInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
info.size = bytes;
|
||||
info.usage = usage;
|
||||
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
if (!detail::check(vkCreateBuffer(device_, &info, nullptr, &buffer), "vkCreateBuffer(object)")) return false;
|
||||
VkMemoryRequirements requirements{};
|
||||
vkGetBufferMemoryRequirements(device_, buffer, &requirements);
|
||||
VkMemoryAllocateInfo allocate{};
|
||||
allocate.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
allocate.allocationSize = requirements.size;
|
||||
allocate.memoryTypeIndex = this->memory_type(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
if (!detail::check(vkAllocateMemory(device_, &allocate, nullptr, &memory), "vkAllocateMemory(object)")) return false;
|
||||
if (!detail::check(vkBindBufferMemory(device_, buffer, memory, 0), "vkBindBufferMemory(object)")) return false;
|
||||
void *mapped = nullptr;
|
||||
if (!detail::check(vkMapMemory(device_, memory, 0, bytes, 0, &mapped), "vkMapMemory(object)")) return false;
|
||||
std::memcpy(mapped, data, static_cast<std::size_t>(bytes));
|
||||
vkUnmapMemory(device_, memory);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Upload the static-map geometry and its texture array. */
|
||||
[[nodiscard]] auto create_object_buffers(const ra3::terrain::gpu_terrain &terrain) -> bool {
|
||||
const auto &scene = terrain.objects;
|
||||
if (scene.empty()) return true;
|
||||
const auto vertex_bytes = static_cast<VkDeviceSize>(scene.vertices.size()) * sizeof(ra3::models::vertex);
|
||||
const auto index_bytes = static_cast<VkDeviceSize>(scene.indices.size()) * sizeof(uint32_t);
|
||||
if (!this->make_host_buffer(scene.vertices.data(), vertex_bytes, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, object_vertex_buffer_, object_vertex_memory_)) return false;
|
||||
if (!this->make_host_buffer(scene.indices.data(), index_bytes, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, object_index_buffer_, object_index_memory_)) return false;
|
||||
object_index_count_ = static_cast<uint32_t>(scene.indices.size());
|
||||
|
||||
const auto size = std::max(1U, scene.texture_size);
|
||||
const auto layers = std::max<size_t>(1U, scene.textures.size());
|
||||
std::vector<uint32_t> pixels(static_cast<size_t>(size) * size * layers, 0xFFFFFFFFU);
|
||||
for (size_t layer = 0; layer < scene.textures.size(); ++layer) {
|
||||
const auto &texture = scene.textures[layer];
|
||||
if (texture.empty()) continue;
|
||||
// The scene already resized every texture to `texture_size`.
|
||||
for (uint32_t y = 0; y < std::min(size, texture.height()); ++y) {
|
||||
for (uint32_t x = 0; x < std::min(size, texture.width()); ++x) {
|
||||
pixels[(static_cast<size_t>(layer) * size + y) * size + x] = texture.data()[static_cast<size_t>(y) * texture.width() + x];
|
||||
}
|
||||
}
|
||||
}
|
||||
return this->make_gpu_image(object_texture_, size, size, VK_FORMAT_B8G8R8A8_UNORM, pixels.data(),
|
||||
static_cast<VkDeviceSize>(pixels.size()) * 4U, this->mip_count(size), VK_FILTER_LINEAR, VK_SAMPLER_MIPMAP_MODE_LINEAR,
|
||||
static_cast<uint32_t>(layers), VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE);
|
||||
}
|
||||
|
||||
/** Full mip chain length for a square texture. */
|
||||
[[nodiscard]] static auto mip_count(uint32_t size) -> uint32_t {
|
||||
uint32_t mips = 1U;
|
||||
while (size > 1U) {
|
||||
size >>= 1U;
|
||||
++mips;
|
||||
}
|
||||
return mips;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto create_object_pipeline() -> bool {
|
||||
VkPushConstantRange push_range{};
|
||||
push_range.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
push_range.size = sizeof(float) * 20U;
|
||||
|
||||
VkPipelineLayoutCreateInfo layout_info{};
|
||||
layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
||||
layout_info.setLayoutCount = 1U;
|
||||
layout_info.pSetLayouts = &descriptor_layout_; // reused: one combined image sampler
|
||||
layout_info.pushConstantRangeCount = 1U;
|
||||
layout_info.pPushConstantRanges = &push_range;
|
||||
if (!detail::check(vkCreatePipelineLayout(device_, &layout_info, nullptr, &object_pipeline_layout_), "vkCreatePipelineLayout(object)")) return false;
|
||||
|
||||
VkShaderModule vertex = VK_NULL_HANDLE;
|
||||
VkShaderModule fragment = VK_NULL_HANDLE;
|
||||
if (!this->make_shader_module(ra3_shaders::object_vert_spv, ra3_shaders::object_vert_spv_words, vertex)) return false;
|
||||
if (!this->make_shader_module(ra3_shaders::object_frag_spv, ra3_shaders::object_frag_spv_words, fragment)) return false;
|
||||
|
||||
VkPipelineShaderStageCreateInfo stages[2]{};
|
||||
stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
||||
stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
|
||||
stages[0].module = vertex;
|
||||
stages[0].pName = "main";
|
||||
stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
||||
stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
stages[1].module = fragment;
|
||||
stages[1].pName = "main";
|
||||
|
||||
VkVertexInputBindingDescription binding{};
|
||||
binding.binding = 0U;
|
||||
binding.stride = sizeof(ra3::models::vertex);
|
||||
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
VkVertexInputAttributeDescription attributes[4]{};
|
||||
attributes[0] = {0U, 0U, VK_FORMAT_R32G32B32_SFLOAT, 0U};
|
||||
attributes[1] = {1U, 0U, VK_FORMAT_R32G32B32_SFLOAT, 12U};
|
||||
attributes[2] = {2U, 0U, VK_FORMAT_R32G32_SFLOAT, 24U};
|
||||
attributes[3] = {3U, 0U, VK_FORMAT_R32_SFLOAT, 32U};
|
||||
static_assert(sizeof(ra3::models::vertex) == 36U, "object vertex layout changed");
|
||||
VkPipelineVertexInputStateCreateInfo vertex_input{};
|
||||
vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
|
||||
vertex_input.vertexBindingDescriptionCount = 1U;
|
||||
vertex_input.pVertexBindingDescriptions = &binding;
|
||||
vertex_input.vertexAttributeDescriptionCount = 4U;
|
||||
vertex_input.pVertexAttributeDescriptions = attributes;
|
||||
|
||||
VkPipelineInputAssemblyStateCreateInfo assembly{};
|
||||
assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
|
||||
assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
VkPipelineViewportStateCreateInfo viewport{};
|
||||
viewport.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
||||
viewport.viewportCount = 1U;
|
||||
viewport.scissorCount = 1U;
|
||||
VkPipelineRasterizationStateCreateInfo raster{};
|
||||
raster.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
|
||||
raster.polygonMode = VK_POLYGON_MODE_FILL;
|
||||
raster.cullMode = VK_CULL_MODE_NONE;
|
||||
raster.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
||||
raster.lineWidth = 1.0F;
|
||||
// Ground decals (sidewalks/roads/deck pieces) sit exactly on the
|
||||
// terrain; a small negative depth bias keeps them from z-fighting.
|
||||
raster.depthBiasEnable = VK_TRUE;
|
||||
raster.depthBiasConstantFactor = -1.0F;
|
||||
raster.depthBiasSlopeFactor = -1.0F;
|
||||
VkPipelineMultisampleStateCreateInfo multisample{};
|
||||
multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
|
||||
multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
||||
VkPipelineDepthStencilStateCreateInfo depth_stencil{};
|
||||
depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
|
||||
depth_stencil.depthTestEnable = VK_TRUE;
|
||||
depth_stencil.depthWriteEnable = VK_TRUE;
|
||||
depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
|
||||
VkPipelineColorBlendAttachmentState blend_attachment{};
|
||||
blend_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
||||
blend_attachment.blendEnable = VK_FALSE;
|
||||
VkPipelineColorBlendStateCreateInfo blend{};
|
||||
blend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
|
||||
blend.attachmentCount = 1U;
|
||||
blend.pAttachments = &blend_attachment;
|
||||
const VkDynamicState dynamic_states[] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
|
||||
VkPipelineDynamicStateCreateInfo dynamic{};
|
||||
dynamic.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
|
||||
dynamic.dynamicStateCount = 2U;
|
||||
dynamic.pDynamicStates = dynamic_states;
|
||||
|
||||
VkGraphicsPipelineCreateInfo pipeline_info{};
|
||||
pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
|
||||
pipeline_info.stageCount = 2U;
|
||||
pipeline_info.pStages = stages;
|
||||
pipeline_info.pVertexInputState = &vertex_input;
|
||||
pipeline_info.pInputAssemblyState = &assembly;
|
||||
pipeline_info.pViewportState = &viewport;
|
||||
pipeline_info.pRasterizationState = &raster;
|
||||
pipeline_info.pMultisampleState = &multisample;
|
||||
pipeline_info.pDepthStencilState = &depth_stencil;
|
||||
pipeline_info.pColorBlendState = &blend;
|
||||
pipeline_info.pDynamicState = &dynamic;
|
||||
pipeline_info.layout = object_pipeline_layout_;
|
||||
pipeline_info.renderPass = render_pass_;
|
||||
pipeline_info.subpass = 0U;
|
||||
const auto created = detail::check(vkCreateGraphicsPipelines(device_, VK_NULL_HANDLE, 1U, &pipeline_info, nullptr, &object_pipeline_),
|
||||
"vkCreateGraphicsPipelines(object)");
|
||||
vkDestroyShaderModule(device_, vertex, nullptr);
|
||||
vkDestroyShaderModule(device_, fragment, nullptr);
|
||||
return created;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto create_object_descriptors() -> bool {
|
||||
if (object_texture_.view == VK_NULL_HANDLE) return false;
|
||||
VkDescriptorSetAllocateInfo set_info{};
|
||||
set_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
||||
set_info.descriptorPool = descriptor_pool_;
|
||||
set_info.descriptorSetCount = 1U;
|
||||
set_info.pSetLayouts = &descriptor_layout_;
|
||||
if (!detail::check(vkAllocateDescriptorSets(device_, &set_info, &object_set_), "vkAllocateDescriptorSets(object)")) return false;
|
||||
VkDescriptorImageInfo info{};
|
||||
info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
info.imageView = object_texture_.view;
|
||||
info.sampler = object_texture_.sampler;
|
||||
VkWriteDescriptorSet write{};
|
||||
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
write.dstSet = object_set_;
|
||||
write.dstBinding = 0U;
|
||||
write.descriptorCount = 1U;
|
||||
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
write.pImageInfo = &info;
|
||||
vkUpdateDescriptorSets(device_, 1U, &write, 0U, nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto draw_terrain(const ra3::terrain::gpu_terrain &terrain, const ra3::render::camera3d &camera, float time_s, float aspect) -> bool {
|
||||
vkWaitForFences(device_, 1U, &in_flight_[current_frame_], VK_TRUE, UINT64_MAX);
|
||||
|
||||
@@ -754,13 +958,16 @@ export namespace ra3::vulkan {
|
||||
|
||||
VkClearValue clear{};
|
||||
clear.color = {{0.45F, 0.55F, 0.70F, 1.0F}};
|
||||
VkClearValue depth_clear{};
|
||||
depth_clear.depthStencil = {1.0F, 0U};
|
||||
const VkClearValue clears[2] = {clear, depth_clear};
|
||||
VkRenderPassBeginInfo render_pass_begin{};
|
||||
render_pass_begin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
|
||||
render_pass_begin.renderPass = render_pass_;
|
||||
render_pass_begin.framebuffer = framebuffers_[image_index];
|
||||
render_pass_begin.renderArea = {{0U, 0U}, swapchain_extent_};
|
||||
render_pass_begin.clearValueCount = 1U;
|
||||
render_pass_begin.pClearValues = &clear;
|
||||
render_pass_begin.clearValueCount = 2U;
|
||||
render_pass_begin.pClearValues = clears;
|
||||
vkCmdBeginRenderPass(cmd, &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
VkViewport viewport{0.0F, 0.0F, static_cast<float>(swapchain_extent_.width), static_cast<float>(swapchain_extent_.height), 0.0F, 1.0F};
|
||||
@@ -793,6 +1000,49 @@ export namespace ra3::vulkan {
|
||||
vkCmdPushConstants(cmd, terrain_pipeline_layout_, VK_SHADER_STAGE_FRAGMENT_BIT, 0U, sizeof(push), push);
|
||||
vkCmdDraw(cmd, 3U, 1U, 0U, 0U);
|
||||
|
||||
// Static-map models (buildings/props) depth-test against the terrain.
|
||||
if (objects_ready_ && object_index_count_ > 0U) {
|
||||
const auto z_scale = terrain.z_scale;
|
||||
const auto world_w = static_cast<float>(terrain.width) * 10.0F;
|
||||
const auto world_h = static_cast<float>(terrain.height) * 10.0F;
|
||||
const auto sample = [&](float wx, float wy) -> float {
|
||||
if (wx < 0.0F || wy < 0.0F || wx >= world_w || wy >= world_h) return -1.0e9F;
|
||||
const auto cx = std::min(terrain.width - 1U, static_cast<uint32_t>(wx / 10.0F));
|
||||
const auto cy = std::min(terrain.height - 1U, static_cast<uint32_t>((world_h - wy) / 10.0F));
|
||||
return static_cast<float>(terrain.heights[static_cast<size_t>(cy) * terrain.width + cx]) * z_scale;
|
||||
};
|
||||
float target_z = sample(camera.target_x, camera.target_y);
|
||||
if (target_z < -1.0e8F) target_z = 0.0F;
|
||||
const auto pitch = std::clamp(camera.pitch, 0.15F, 1.45F);
|
||||
const auto fov = std::clamp(camera.fov, 0.3F, 1.4F);
|
||||
const auto cp = std::cos(pitch);
|
||||
const float fwd[3] = {cp * std::sin(camera.yaw), cp * std::cos(camera.yaw), -std::sin(pitch)};
|
||||
float right[3] = {fwd[1], -fwd[0], 0.0F};
|
||||
const auto rl = std::sqrt(right[0] * right[0] + right[1] * right[1]);
|
||||
right[0] /= rl;
|
||||
right[1] /= rl;
|
||||
float up[3] = {right[1] * fwd[2], -right[0] * fwd[2], right[0] * fwd[1] - right[1] * fwd[0]};
|
||||
const auto ul = std::sqrt(up[0] * up[0] + up[1] * up[1] + up[2] * up[2]);
|
||||
up[0] /= ul;
|
||||
up[1] /= ul;
|
||||
up[2] /= ul;
|
||||
const auto dist = camera.height / std::sin(pitch);
|
||||
const float cam[3] = {camera.target_x - fwd[0] * dist, camera.target_y - fwd[1] * dist, target_z + camera.height - fwd[2] * dist};
|
||||
const auto th = std::tan(fov * 0.5F);
|
||||
const float obj_push[20] = {cam[0], cam[1], cam[2], 0.0F,
|
||||
fwd[0], fwd[1], fwd[2], 0.0F,
|
||||
right[0], right[1], right[2], th,
|
||||
up[0], up[1], up[2], th * aspect,
|
||||
0.45F, 0.35F, 0.82F, 0.38F};
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, object_pipeline_);
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, object_pipeline_layout_, 0U, 1U, &object_set_, 0U, nullptr);
|
||||
vkCmdPushConstants(cmd, object_pipeline_layout_, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0U, sizeof(obj_push), obj_push);
|
||||
const VkDeviceSize offset = 0U;
|
||||
vkCmdBindVertexBuffers(cmd, 0U, 1U, &object_vertex_buffer_, &offset);
|
||||
vkCmdBindIndexBuffer(cmd, object_index_buffer_, 0U, VK_INDEX_TYPE_UINT32);
|
||||
vkCmdDrawIndexed(cmd, object_index_count_, 1U, 0U, 0, 0U);
|
||||
}
|
||||
|
||||
// Overlays (top-left FPS label, bottom-right minimap) use the image
|
||||
// pipeline with alpha blending, drawn into the same render pass.
|
||||
const auto draw_overlay = [&](const gpu_image &overlay, VkDescriptorSet set, uint32_t w, uint32_t h, float x, float y) {
|
||||
@@ -861,6 +1111,27 @@ export namespace ra3::vulkan {
|
||||
terrain_set_ = VK_NULL_HANDLE;
|
||||
overlay_label_set_ = VK_NULL_HANDLE;
|
||||
overlay_minimap_set_ = VK_NULL_HANDLE;
|
||||
|
||||
if (object_vertex_buffer_ != VK_NULL_HANDLE) vkDestroyBuffer(device_, object_vertex_buffer_, nullptr);
|
||||
if (object_index_buffer_ != VK_NULL_HANDLE) vkDestroyBuffer(device_, object_index_buffer_, nullptr);
|
||||
if (object_vertex_memory_ != VK_NULL_HANDLE) vkFreeMemory(device_, object_vertex_memory_, nullptr);
|
||||
if (object_index_memory_ != VK_NULL_HANDLE) vkFreeMemory(device_, object_index_memory_, nullptr);
|
||||
if (object_texture_.sampler != VK_NULL_HANDLE) vkDestroySampler(device_, object_texture_.sampler, nullptr);
|
||||
if (object_texture_.view != VK_NULL_HANDLE) vkDestroyImageView(device_, object_texture_.view, nullptr);
|
||||
if (object_texture_.image != VK_NULL_HANDLE) vkDestroyImage(device_, object_texture_.image, nullptr);
|
||||
if (object_texture_.memory != VK_NULL_HANDLE) vkFreeMemory(device_, object_texture_.memory, nullptr);
|
||||
if (object_pipeline_ != VK_NULL_HANDLE) vkDestroyPipeline(device_, object_pipeline_, nullptr);
|
||||
if (object_pipeline_layout_ != VK_NULL_HANDLE) vkDestroyPipelineLayout(device_, object_pipeline_layout_, nullptr);
|
||||
object_vertex_buffer_ = VK_NULL_HANDLE;
|
||||
object_index_buffer_ = VK_NULL_HANDLE;
|
||||
object_vertex_memory_ = VK_NULL_HANDLE;
|
||||
object_index_memory_ = VK_NULL_HANDLE;
|
||||
object_texture_ = {};
|
||||
object_pipeline_ = VK_NULL_HANDLE;
|
||||
object_pipeline_layout_ = VK_NULL_HANDLE;
|
||||
object_set_ = VK_NULL_HANDLE;
|
||||
object_index_count_ = 0U;
|
||||
objects_ready_ = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1009,23 +1280,78 @@ export namespace ra3::vulkan {
|
||||
}
|
||||
|
||||
framebuffers_.resize(actual);
|
||||
for (uint32_t i = 0; i < actual; ++i) {
|
||||
VkImageView attachments[] = {swapchain_views_[i]};
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Create one color+depth framebuffer per swapchain image (needs `render_pass_`). */
|
||||
[[nodiscard]] auto create_framebuffers() -> bool {
|
||||
this->create_depth_resources();
|
||||
for (size_t i = 0; i < framebuffers_.size(); ++i) {
|
||||
VkImageView attachments[] = {swapchain_views_[i], depth_views_[i]};
|
||||
VkFramebufferCreateInfo framebuffer_info{};
|
||||
framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||
framebuffer_info.renderPass = render_pass_;
|
||||
framebuffer_info.attachmentCount = 1U;
|
||||
framebuffer_info.attachmentCount = 2U;
|
||||
framebuffer_info.pAttachments = attachments;
|
||||
framebuffer_info.width = extent.width;
|
||||
framebuffer_info.height = extent.height;
|
||||
framebuffer_info.width = swapchain_extent_.width;
|
||||
framebuffer_info.height = swapchain_extent_.height;
|
||||
framebuffer_info.layers = 1U;
|
||||
if (!detail::check(vkCreateFramebuffer(device_, &framebuffer_info, nullptr, &framebuffers_[i]), "vkCreateFramebuffer")) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Allocate a depth image + view per swapchain image (static-map models depth-test against the terrain). */
|
||||
auto create_depth_resources() -> void {
|
||||
this->destroy_depth_resources();
|
||||
depth_images_.resize(swapchain_images_.size());
|
||||
depth_memories_.resize(swapchain_images_.size());
|
||||
depth_views_.resize(swapchain_images_.size());
|
||||
for (size_t i = 0; i < swapchain_images_.size(); ++i) {
|
||||
VkImageCreateInfo image_info{};
|
||||
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
image_info.imageType = VK_IMAGE_TYPE_2D;
|
||||
image_info.extent = {swapchain_extent_.width, swapchain_extent_.height, 1U};
|
||||
image_info.mipLevels = 1U;
|
||||
image_info.arrayLayers = 1U;
|
||||
image_info.format = VK_FORMAT_D32_SFLOAT;
|
||||
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
image_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
if (vkCreateImage(device_, &image_info, nullptr, &depth_images_[i]) != VK_SUCCESS) return;
|
||||
VkMemoryRequirements requirements{};
|
||||
vkGetImageMemoryRequirements(device_, depth_images_[i], &requirements);
|
||||
VkMemoryAllocateInfo allocate{};
|
||||
allocate.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
allocate.allocationSize = requirements.size;
|
||||
allocate.memoryTypeIndex = this->memory_type(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
if (vkAllocateMemory(device_, &allocate, nullptr, &depth_memories_[i]) != VK_SUCCESS) return;
|
||||
vkBindImageMemory(device_, depth_images_[i], depth_memories_[i], 0);
|
||||
VkImageViewCreateInfo view_info{};
|
||||
view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
view_info.image = depth_images_[i];
|
||||
view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
view_info.format = VK_FORMAT_D32_SFLOAT;
|
||||
view_info.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0U, 1U, 0U, 1U};
|
||||
if (vkCreateImageView(device_, &view_info, nullptr, &depth_views_[i]) != VK_SUCCESS) return;
|
||||
}
|
||||
}
|
||||
|
||||
auto destroy_depth_resources() -> void {
|
||||
if (device_ == VK_NULL_HANDLE) return;
|
||||
for (const auto view: depth_views_) vkDestroyImageView(device_, view, nullptr);
|
||||
for (const auto image: depth_images_) vkDestroyImage(device_, image, nullptr);
|
||||
for (const auto memory: depth_memories_) vkFreeMemory(device_, memory, nullptr);
|
||||
depth_views_.clear();
|
||||
depth_images_.clear();
|
||||
depth_memories_.clear();
|
||||
}
|
||||
|
||||
auto cleanup_swapchain() -> void {
|
||||
for (const auto framebuffer: framebuffers_) vkDestroyFramebuffer(device_, framebuffer, nullptr);
|
||||
this->destroy_depth_resources();
|
||||
for (const auto view: swapchain_views_) vkDestroyImageView(device_, view, nullptr);
|
||||
framebuffers_.clear();
|
||||
swapchain_views_.clear();
|
||||
@@ -1041,7 +1367,7 @@ export namespace ra3::vulkan {
|
||||
if (width == 0 || height == 0) return false;
|
||||
vkDeviceWaitIdle(device_);
|
||||
this->cleanup_swapchain();
|
||||
return this->create_swapchain();
|
||||
return this->create_swapchain() && this->create_framebuffers();
|
||||
}
|
||||
|
||||
// ---- pipeline --------------------------------------------------------
|
||||
@@ -1058,22 +1384,36 @@ export namespace ra3::vulkan {
|
||||
color.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
|
||||
VkAttachmentReference color_ref{0U, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
||||
|
||||
VkAttachmentDescription depth{};
|
||||
depth.format = VK_FORMAT_D32_SFLOAT;
|
||||
depth.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
depth.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
depth.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
depth.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
depth.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
depth.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
depth.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
VkAttachmentReference depth_ref{1U, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL};
|
||||
|
||||
VkSubpassDescription subpass{};
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.colorAttachmentCount = 1U;
|
||||
subpass.pColorAttachments = &color_ref;
|
||||
subpass.pDepthStencilAttachment = &depth_ref;
|
||||
|
||||
VkSubpassDependency dependency{};
|
||||
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependency.dstSubpass = 0U;
|
||||
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
|
||||
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
|
||||
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
|
||||
const VkAttachmentDescription attachments[] = {color, depth};
|
||||
VkRenderPassCreateInfo render_pass_info{};
|
||||
render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
||||
render_pass_info.attachmentCount = 1U;
|
||||
render_pass_info.pAttachments = &color;
|
||||
render_pass_info.attachmentCount = 2U;
|
||||
render_pass_info.pAttachments = attachments;
|
||||
render_pass_info.subpassCount = 1U;
|
||||
render_pass_info.pSubpasses = &subpass;
|
||||
render_pass_info.dependencyCount = 1U;
|
||||
@@ -1211,10 +1551,10 @@ export namespace ra3::vulkan {
|
||||
command_buffers_.resize(frames_in_flight);
|
||||
if (!detail::check(vkAllocateCommandBuffers(device_, &allocate, command_buffers_.data()), "vkAllocateCommandBuffers")) return false;
|
||||
|
||||
VkDescriptorPoolSize pool_size{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 4U};
|
||||
VkDescriptorPoolSize pool_size{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 8U};
|
||||
VkDescriptorPoolCreateInfo descriptor_pool_info{};
|
||||
descriptor_pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
||||
descriptor_pool_info.maxSets = 4U;
|
||||
descriptor_pool_info.maxSets = 8U;
|
||||
descriptor_pool_info.poolSizeCount = 1U;
|
||||
descriptor_pool_info.pPoolSizes = &pool_size;
|
||||
if (!detail::check(vkCreateDescriptorPool(device_, &descriptor_pool_info, nullptr, &descriptor_pool_), "vkCreateDescriptorPool")) return false;
|
||||
@@ -1307,13 +1647,16 @@ export namespace ra3::vulkan {
|
||||
|
||||
VkClearValue clear{};
|
||||
clear.color = {{0.05F, 0.06F, 0.08F, 1.0F}};
|
||||
VkClearValue depth_clear{};
|
||||
depth_clear.depthStencil = {1.0F, 0U};
|
||||
const VkClearValue clears[2] = {clear, depth_clear};
|
||||
VkRenderPassBeginInfo render_pass_begin{};
|
||||
render_pass_begin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
|
||||
render_pass_begin.renderPass = render_pass_;
|
||||
render_pass_begin.framebuffer = framebuffers_[image_index];
|
||||
render_pass_begin.renderArea = {{0U, 0U}, swapchain_extent_};
|
||||
render_pass_begin.clearValueCount = 1U;
|
||||
render_pass_begin.pClearValues = &clear;
|
||||
render_pass_begin.clearValueCount = 2U;
|
||||
render_pass_begin.pClearValues = clears;
|
||||
vkCmdBeginRenderPass(cmd, &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
VkViewport viewport{0.0F, 0.0F, static_cast<float>(swapchain_extent_.width), static_cast<float>(swapchain_extent_.height), 0.0F, 1.0F};
|
||||
@@ -1433,6 +1776,9 @@ export namespace ra3::vulkan {
|
||||
std::vector<VkImage> swapchain_images_;
|
||||
std::vector<VkImageView> swapchain_views_;
|
||||
std::vector<VkFramebuffer> framebuffers_;
|
||||
std::vector<VkImage> depth_images_;
|
||||
std::vector<VkDeviceMemory> depth_memories_;
|
||||
std::vector<VkImageView> depth_views_;
|
||||
VkRenderPass render_pass_ = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout descriptor_layout_ = VK_NULL_HANDLE;
|
||||
VkPipelineLayout pipeline_layout_ = VK_NULL_HANDLE;
|
||||
@@ -1464,6 +1810,16 @@ export namespace ra3::vulkan {
|
||||
VkPipeline terrain_pipeline_ = VK_NULL_HANDLE;
|
||||
VkDescriptorPool terrain_pool_ = VK_NULL_HANDLE;
|
||||
VkDescriptorSet terrain_set_ = VK_NULL_HANDLE;
|
||||
VkBuffer object_vertex_buffer_ = VK_NULL_HANDLE;
|
||||
VkDeviceMemory object_vertex_memory_ = VK_NULL_HANDLE;
|
||||
VkBuffer object_index_buffer_ = VK_NULL_HANDLE;
|
||||
VkDeviceMemory object_index_memory_ = VK_NULL_HANDLE;
|
||||
uint32_t object_index_count_ = 0U;
|
||||
gpu_image object_texture_;
|
||||
VkPipelineLayout object_pipeline_layout_ = VK_NULL_HANDLE;
|
||||
VkPipeline object_pipeline_ = VK_NULL_HANDLE;
|
||||
VkDescriptorSet object_set_ = VK_NULL_HANDLE;
|
||||
bool objects_ready_ = false;
|
||||
std::vector<VkSemaphore> image_available_;
|
||||
std::vector<VkSemaphore> render_finished_;
|
||||
std::vector<VkFence> in_flight_;
|
||||
|
||||
Reference in New Issue
Block a user