Fix minimap MSAA pipeline, defer swapchain recreation, fix player character spawn order

- Add .setMultisample() to minimap display pipeline and recreatePipelines() for MSAA changes
- Defer all swapchain recreation in window.cpp to beginFrame() via markSwapchainDirty()
  to prevent mid-frame render pass destruction crashes on resolution/fullscreen change
- Move spawnPlayerCharacter() call to after loadTestTerrain() where character renderer exists
This commit is contained in:
Kelsi 2026-02-22 03:49:44 -08:00
parent ebd0084c22
commit e8e859384e
6 changed files with 57 additions and 3 deletions

View file

@ -25,6 +25,7 @@ public:
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout, int size = 200);
void shutdown();
void recreatePipelines();
void setAssetManager(pipeline::AssetManager* am) { assetManager = am; }
void setMapName(const std::string& name);

View file

@ -70,6 +70,7 @@ public:
const std::vector<VkFramebuffer>& getSwapchainFramebuffers() const { return swapchainFramebuffers; }
bool isSwapchainDirty() const { return swapchainDirty; }
void markSwapchainDirty() { swapchainDirty = true; }
// MSAA
VkSampleCountFlagBits getMsaaSamples() const { return msaaSamples_; }

View file

@ -3099,6 +3099,13 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
LOG_INFO("Online world terrain loading initiated");
}
// Character renderer is created inside loadTestTerrain(), so spawn the
// player model now that the renderer actually exists.
if (!playerCharacterSpawned) {
spawnPlayerCharacter();
loadEquippedWeapons();
}
showProgress("Streaming terrain tiles...", 0.35f);
// Wait for surrounding terrain tiles to stream in

View file

@ -89,7 +89,7 @@ void Window::pollEvents() {
width = event.window.data1;
height = event.window.data2;
if (vkContext) {
vkContext->recreateSwapchain(width, height);
vkContext->markSwapchainDirty();
}
LOG_DEBUG("Window resized to ", width, "x", height);
}
@ -120,7 +120,7 @@ void Window::setFullscreen(bool enable) {
height = windowedHeight;
}
if (vkContext) {
vkContext->recreateSwapchain(width, height);
vkContext->markSwapchainDirty();
}
}
@ -145,7 +145,7 @@ void Window::applyResolution(int w, int h) {
windowedWidth = w;
windowedHeight = h;
if (vkContext) {
vkContext->recreateSwapchain(width, height);
vkContext->markSwapchainDirty();
}
}

View file

@ -187,6 +187,7 @@ bool Minimap::initialize(VkContext* ctx, VkDescriptorSetLayout /*perFrameLayout*
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
.setNoDepthTest()
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
.setMultisample(vkCtx->getMsaaSamples())
.setLayout(displayPipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
@ -233,6 +234,48 @@ void Minimap::shutdown() {
vkCtx = nullptr;
}
void Minimap::recreatePipelines() {
if (!vkCtx || !displayPipelineLayout) return;
VkDevice device = vkCtx->getDevice();
if (displayPipeline) { vkDestroyPipeline(device, displayPipeline, nullptr); displayPipeline = VK_NULL_HANDLE; }
VkShaderModule vs, fs;
if (!vs.loadFromFile(device, "assets/shaders/minimap_display.vert.spv") ||
!fs.loadFromFile(device, "assets/shaders/minimap_display.frag.spv")) {
LOG_ERROR("Minimap: failed to reload display shaders for pipeline recreation");
return;
}
VkVertexInputBindingDescription binding{};
binding.binding = 0;
binding.stride = 4 * sizeof(float);
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
std::vector<VkVertexInputAttributeDescription> attrs(2);
attrs[0] = { 0, 0, VK_FORMAT_R32G32_SFLOAT, 0 };
attrs[1] = { 1, 0, VK_FORMAT_R32G32_SFLOAT, 2 * sizeof(float) };
displayPipeline = PipelineBuilder()
.setShaders(vs.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
fs.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
.setVertexInput({ binding }, attrs)
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
.setNoDepthTest()
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
.setMultisample(vkCtx->getMsaaSamples())
.setLayout(displayPipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
.build(device);
vs.destroy();
fs.destroy();
LOG_INFO("Minimap: display pipeline recreated with MSAA ", static_cast<int>(vkCtx->getMsaaSamples()), "x");
}
void Minimap::setMapName(const std::string& name) {
if (mapName != name) {
mapName = name;

View file

@ -778,6 +778,8 @@ void Renderer::applyMsaaChange() {
if (auto* lf = skySystem->getLensFlare()) lf->recreatePipelines();
}
if (minimap) minimap->recreatePipelines();
// Selection circle + overlay use lazy init, just destroy them
VkDevice device = vkCtx->getDevice();
if (selCirclePipeline) { vkDestroyPipeline(device, selCirclePipeline, nullptr); selCirclePipeline = VK_NULL_HANDLE; }