mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-27 09:03:51 +00:00
Create a VkPipelineCache at device init, loaded from disk if available. All 65 pipeline creation calls across 19 renderer files now use the shared cache. On shutdown, the cache is serialized to disk so subsequent launches skip redundant shader compilation. Cache path: ~/.local/share/wowee/pipeline_cache.bin (Linux), ~/Library/Caches/wowee/ (macOS), %APPDATA%\wowee\ (Windows). Stale/corrupt caches are handled gracefully (fallback to empty cache).
213 lines
7.1 KiB
C++
213 lines
7.1 KiB
C++
#include "rendering/skybox.hpp"
|
|
#include "rendering/sky_system.hpp"
|
|
#include "rendering/vk_context.hpp"
|
|
#include "rendering/vk_shader.hpp"
|
|
#include "rendering/vk_pipeline.hpp"
|
|
#include "rendering/vk_frame_data.hpp"
|
|
#include "core/logger.hpp"
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
#include <cmath>
|
|
|
|
namespace wowee {
|
|
namespace rendering {
|
|
|
|
// Push constant struct — must match skybox.frag.glsl layout
|
|
struct SkyPushConstants {
|
|
glm::vec4 zenithColor; // DBC skyTopColor
|
|
glm::vec4 midColor; // DBC skyMiddleColor
|
|
glm::vec4 horizonColor; // DBC skyBand1Color
|
|
glm::vec4 fogColor; // DBC skyBand2Color / fogColor blend
|
|
glm::vec4 sunDirAndTime; // xyz = sun direction, w = timeOfDay
|
|
};
|
|
static_assert(sizeof(SkyPushConstants) == 80, "SkyPushConstants size mismatch");
|
|
|
|
Skybox::Skybox() = default;
|
|
|
|
Skybox::~Skybox() {
|
|
shutdown();
|
|
}
|
|
|
|
bool Skybox::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) {
|
|
LOG_INFO("Initializing skybox");
|
|
|
|
vkCtx = ctx;
|
|
|
|
VkDevice device = vkCtx->getDevice();
|
|
|
|
// Load SPIR-V shaders
|
|
VkShaderModule vertModule;
|
|
if (!vertModule.loadFromFile(device, "assets/shaders/skybox.vert.spv")) {
|
|
LOG_ERROR("Failed to load skybox vertex shader");
|
|
return false;
|
|
}
|
|
|
|
VkShaderModule fragModule;
|
|
if (!fragModule.loadFromFile(device, "assets/shaders/skybox.frag.spv")) {
|
|
LOG_ERROR("Failed to load skybox fragment shader");
|
|
return false;
|
|
}
|
|
|
|
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
|
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
|
|
|
// Push constant range: 5 x vec4 = 80 bytes
|
|
VkPushConstantRange pushRange{};
|
|
pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
pushRange.offset = 0;
|
|
pushRange.size = sizeof(SkyPushConstants); // 80 bytes
|
|
|
|
// Create pipeline layout with perFrameLayout (set 0) + push constants
|
|
pipelineLayout = createPipelineLayout(device, {perFrameLayout}, {pushRange});
|
|
if (pipelineLayout == VK_NULL_HANDLE) {
|
|
LOG_ERROR("Failed to create skybox pipeline layout");
|
|
return false;
|
|
}
|
|
|
|
// Fullscreen triangle — no vertex buffer, no vertex input.
|
|
// Dynamic viewport and scissor
|
|
std::vector<VkDynamicState> dynamicStates = {
|
|
VK_DYNAMIC_STATE_VIEWPORT,
|
|
VK_DYNAMIC_STATE_SCISSOR
|
|
};
|
|
|
|
pipeline = PipelineBuilder()
|
|
.setShaders(vertStage, fragStage)
|
|
.setVertexInput({}, {})
|
|
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
|
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
|
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL) // depth test on, write off, LEQUAL for far plane
|
|
.setColorBlendAttachment(PipelineBuilder::blendDisabled())
|
|
.setMultisample(vkCtx->getMsaaSamples())
|
|
.setLayout(pipelineLayout)
|
|
.setRenderPass(vkCtx->getImGuiRenderPass())
|
|
.setDynamicStates(dynamicStates)
|
|
.build(device, vkCtx->getPipelineCache());
|
|
|
|
// Shader modules can be freed after pipeline creation
|
|
vertModule.destroy();
|
|
fragModule.destroy();
|
|
|
|
if (pipeline == VK_NULL_HANDLE) {
|
|
LOG_ERROR("Failed to create skybox pipeline");
|
|
return false;
|
|
}
|
|
|
|
LOG_INFO("Skybox initialized");
|
|
return true;
|
|
}
|
|
|
|
void Skybox::recreatePipelines() {
|
|
if (!vkCtx) return;
|
|
VkDevice device = vkCtx->getDevice();
|
|
|
|
if (pipeline != VK_NULL_HANDLE) { vkDestroyPipeline(device, pipeline, nullptr); pipeline = VK_NULL_HANDLE; }
|
|
|
|
VkShaderModule vertModule;
|
|
if (!vertModule.loadFromFile(device, "assets/shaders/skybox.vert.spv")) {
|
|
LOG_ERROR("Skybox::recreatePipelines: failed to load vertex shader");
|
|
return;
|
|
}
|
|
VkShaderModule fragModule;
|
|
if (!fragModule.loadFromFile(device, "assets/shaders/skybox.frag.spv")) {
|
|
LOG_ERROR("Skybox::recreatePipelines: failed to load fragment shader");
|
|
vertModule.destroy();
|
|
return;
|
|
}
|
|
|
|
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
|
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
|
|
|
std::vector<VkDynamicState> dynamicStates = {
|
|
VK_DYNAMIC_STATE_VIEWPORT,
|
|
VK_DYNAMIC_STATE_SCISSOR
|
|
};
|
|
|
|
pipeline = PipelineBuilder()
|
|
.setShaders(vertStage, fragStage)
|
|
.setVertexInput({}, {})
|
|
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
|
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
|
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL)
|
|
.setColorBlendAttachment(PipelineBuilder::blendDisabled())
|
|
.setMultisample(vkCtx->getMsaaSamples())
|
|
.setLayout(pipelineLayout)
|
|
.setRenderPass(vkCtx->getImGuiRenderPass())
|
|
.setDynamicStates(dynamicStates)
|
|
.build(device, vkCtx->getPipelineCache());
|
|
|
|
vertModule.destroy();
|
|
fragModule.destroy();
|
|
|
|
if (pipeline == VK_NULL_HANDLE) {
|
|
LOG_ERROR("Skybox::recreatePipelines: failed to create pipeline");
|
|
}
|
|
}
|
|
|
|
void Skybox::shutdown() {
|
|
if (vkCtx) {
|
|
VkDevice device = vkCtx->getDevice();
|
|
if (pipeline != VK_NULL_HANDLE) {
|
|
vkDestroyPipeline(device, pipeline, nullptr);
|
|
pipeline = VK_NULL_HANDLE;
|
|
}
|
|
if (pipelineLayout != VK_NULL_HANDLE) {
|
|
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
|
|
pipelineLayout = VK_NULL_HANDLE;
|
|
}
|
|
}
|
|
|
|
vkCtx = nullptr;
|
|
}
|
|
|
|
void Skybox::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const SkyParams& params) {
|
|
if (pipeline == VK_NULL_HANDLE || !renderingEnabled) {
|
|
return;
|
|
}
|
|
|
|
// Compute sun direction from directionalDir (light points toward scene, sun is opposite)
|
|
glm::vec3 sunDir = -glm::normalize(params.directionalDir);
|
|
|
|
SkyPushConstants push{};
|
|
push.zenithColor = glm::vec4(params.skyTopColor, 1.0f);
|
|
push.midColor = glm::vec4(params.skyMiddleColor, 1.0f);
|
|
push.horizonColor = glm::vec4(params.skyBand1Color, 1.0f);
|
|
push.fogColor = glm::vec4(params.skyBand2Color, 1.0f);
|
|
push.sunDirAndTime = glm::vec4(sunDir, params.timeOfDay);
|
|
|
|
// Bind pipeline
|
|
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
|
|
|
// Bind per-frame descriptor set (set 0 — camera UBO)
|
|
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout,
|
|
0, 1, &perFrameSet, 0, nullptr);
|
|
|
|
// Push constants
|
|
vkCmdPushConstants(cmd, pipelineLayout,
|
|
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
|
|
0, sizeof(push), &push);
|
|
|
|
// Draw fullscreen triangle — no vertex buffer needed
|
|
vkCmdDraw(cmd, 3, 1, 0, 0);
|
|
}
|
|
|
|
void Skybox::update(float deltaTime) {
|
|
if (timeProgressionEnabled) {
|
|
timeOfDay += deltaTime * timeSpeed;
|
|
|
|
// Wrap around 24 hours
|
|
if (timeOfDay >= 24.0f) {
|
|
timeOfDay -= 24.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Skybox::setTimeOfDay(float time) {
|
|
// Clamp to 0-24 range
|
|
while (time < 0.0f) time += 24.0f;
|
|
while (time >= 24.0f) time -= 24.0f;
|
|
|
|
timeOfDay = time;
|
|
}
|
|
|
|
} // namespace rendering
|
|
} // namespace wowee
|