Vulcan Nightmare

Experimentally bringing up vulcan support
This commit is contained in:
Kelsi 2026-02-21 19:41:21 -08:00
parent 863a786c48
commit 83b576e8d9
189 changed files with 12147 additions and 7820 deletions

View file

@ -1,11 +1,14 @@
#include "rendering/starfield.hpp"
#include "rendering/shader.hpp"
#include "rendering/camera.hpp"
#include "rendering/vk_context.hpp"
#include "rendering/vk_shader.hpp"
#include "rendering/vk_pipeline.hpp"
#include "rendering/vk_frame_data.hpp"
#include "rendering/vk_utils.hpp"
#include "core/logger.hpp"
#include <GL/glew.h>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/glm.hpp>
#include <cmath>
#include <random>
#include <vector>
namespace wowee {
namespace rendering {
@ -16,76 +19,89 @@ StarField::~StarField() {
shutdown();
}
bool StarField::initialize() {
bool StarField::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) {
LOG_INFO("Initializing star field");
// Create star shader
starShader = std::make_unique<Shader>();
vkCtx = ctx;
VkDevice device = vkCtx->getDevice();
// Vertex shader - simple point rendering
const char* vertexShaderSource = R"(
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in float aBrightness;
layout (location = 2) in float aTwinklePhase;
uniform mat4 view;
uniform mat4 projection;
uniform float time;
uniform float intensity;
out float Brightness;
void main() {
// Remove translation from view matrix (stars are infinitely far)
mat4 viewNoTranslation = mat4(mat3(view));
gl_Position = projection * viewNoTranslation * vec4(aPos, 1.0);
// Twinkle effect (subtle brightness variation)
float twinkle = sin(time * 2.0 + aTwinklePhase) * 0.2 + 0.8; // 0.6 to 1.0
Brightness = aBrightness * twinkle * intensity;
// Point size based on brightness
gl_PointSize = 2.0 + aBrightness * 2.0; // 2-4 pixels
}
)";
// Fragment shader - star color
const char* fragmentShaderSource = R"(
#version 330 core
in float Brightness;
out vec4 FragColor;
void main() {
// Circular point (not square)
vec2 coord = gl_PointCoord - vec2(0.5);
float dist = length(coord);
if (dist > 0.5) {
discard;
}
// Soften edges
float alpha = smoothstep(0.5, 0.3, dist);
// Star color (slightly blue-white)
vec3 starColor = vec3(0.9, 0.95, 1.0);
FragColor = vec4(starColor * Brightness, alpha * Brightness);
}
)";
if (!starShader->loadFromSource(vertexShaderSource, fragmentShaderSource)) {
LOG_ERROR("Failed to create star shader");
// Load SPIR-V shaders
VkShaderModule vertModule;
if (!vertModule.loadFromFile(device, "assets/shaders/starfield.vert.spv")) {
LOG_ERROR("Failed to load starfield vertex shader");
return false;
}
// Generate random stars
generateStars();
VkShaderModule fragModule;
if (!fragModule.loadFromFile(device, "assets/shaders/starfield.frag.spv")) {
LOG_ERROR("Failed to load starfield fragment shader");
return false;
}
// Create OpenGL buffers
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
// Push constants: float time + float intensity = 8 bytes
VkPushConstantRange pushRange{};
pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
pushRange.offset = 0;
pushRange.size = sizeof(float) * 2; // time, intensity
// Pipeline layout: set 0 = per-frame UBO, push constants
pipelineLayout = createPipelineLayout(device, {perFrameLayout}, {pushRange});
if (pipelineLayout == VK_NULL_HANDLE) {
LOG_ERROR("Failed to create starfield pipeline layout");
return false;
}
// Vertex input: binding 0, stride = 5 * sizeof(float)
// location 0: vec3 pos (offset 0)
// location 1: float brightness (offset 12)
// location 2: float twinklePhase (offset 16)
VkVertexInputBindingDescription binding{};
binding.binding = 0;
binding.stride = 5 * sizeof(float);
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
VkVertexInputAttributeDescription posAttr{};
posAttr.location = 0;
posAttr.binding = 0;
posAttr.format = VK_FORMAT_R32G32B32_SFLOAT;
posAttr.offset = 0;
VkVertexInputAttributeDescription brightnessAttr{};
brightnessAttr.location = 1;
brightnessAttr.binding = 0;
brightnessAttr.format = VK_FORMAT_R32_SFLOAT;
brightnessAttr.offset = 3 * sizeof(float);
VkVertexInputAttributeDescription twinkleAttr{};
twinkleAttr.location = 2;
twinkleAttr.binding = 0;
twinkleAttr.format = VK_FORMAT_R32_SFLOAT;
twinkleAttr.offset = 4 * sizeof(float);
pipeline = PipelineBuilder()
.setShaders(vertStage, fragStage)
.setVertexInput({binding}, {posAttr, brightnessAttr, twinkleAttr})
.setTopology(VK_PRIMITIVE_TOPOLOGY_POINT_LIST)
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL) // depth test, no write (stars behind sky)
.setColorBlendAttachment(PipelineBuilder::blendAdditive())
.setLayout(pipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass())
.build(device);
vertModule.destroy();
fragModule.destroy();
if (pipeline == VK_NULL_HANDLE) {
LOG_ERROR("Failed to create starfield pipeline");
return false;
}
// Generate star positions and upload to GPU
generateStars();
createStarBuffers();
LOG_INFO("Star field initialized: ", starCount, " stars");
@ -94,62 +110,65 @@ bool StarField::initialize() {
void StarField::shutdown() {
destroyStarBuffers();
starShader.reset();
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;
stars.clear();
}
void StarField::render(const Camera& camera, float timeOfDay,
float cloudDensity, float fogDensity) {
if (!renderingEnabled || vao == 0 || !starShader || stars.empty()) {
void StarField::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet,
float timeOfDay, float cloudDensity, float fogDensity) {
if (!renderingEnabled || pipeline == VK_NULL_HANDLE || vertexBuffer == VK_NULL_HANDLE
|| stars.empty()) {
return;
}
// Get star intensity based on time of day
// Compute intensity from time of day then attenuate for clouds/fog
float intensity = getStarIntensity(timeOfDay);
// Reduce intensity based on cloud density and fog (more clouds/fog = fewer visible stars)
intensity *= (1.0f - glm::clamp(cloudDensity * 0.7f, 0.0f, 1.0f));
intensity *= (1.0f - glm::clamp(fogDensity * 0.3f, 0.0f, 1.0f));
// Don't render if stars would be invisible
if (intensity <= 0.01f) {
return;
}
// Enable blending for star glow
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Push constants: time and intensity
struct StarPushConstants {
float time;
float intensity;
};
StarPushConstants push{twinkleTime, intensity};
// Enable point sprites
glEnable(GL_PROGRAM_POINT_SIZE);
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
// Disable depth writing (stars are background)
glDepthMask(GL_FALSE);
// Bind per-frame descriptor set (set 0 — camera UBO with view/projection)
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout,
0, 1, &perFrameSet, 0, nullptr);
starShader->use();
vkCmdPushConstants(cmd, pipelineLayout,
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
0, sizeof(push), &push);
// Set uniforms
glm::mat4 view = camera.getViewMatrix();
glm::mat4 projection = camera.getProjectionMatrix();
// Bind vertex buffer
VkDeviceSize offset = 0;
vkCmdBindVertexBuffers(cmd, 0, 1, &vertexBuffer, &offset);
starShader->setUniform("view", view);
starShader->setUniform("projection", projection);
starShader->setUniform("time", twinkleTime);
starShader->setUniform("intensity", intensity);
// Render stars as points
glBindVertexArray(vao);
glDrawArrays(GL_POINTS, 0, starCount);
glBindVertexArray(0);
// Restore state
glDepthMask(GL_TRUE);
glDisable(GL_PROGRAM_POINT_SIZE);
glDisable(GL_BLEND);
// Draw all stars as individual points
vkCmdDraw(cmd, static_cast<uint32_t>(starCount), 1, 0, 0);
}
void StarField::update(float deltaTime) {
// Update twinkle animation
twinkleTime += deltaTime;
}
@ -157,30 +176,27 @@ void StarField::generateStars() {
stars.clear();
stars.reserve(starCount);
// Random number generator
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<float> phiDist(0.0f, M_PI / 2.0f); // 0 to 90 degrees (hemisphere)
std::uniform_real_distribution<float> thetaDist(0.0f, 2.0f * M_PI); // 0 to 360 degrees
std::uniform_real_distribution<float> brightnessDist(0.3f, 1.0f); // Varying brightness
std::uniform_real_distribution<float> twinkleDist(0.0f, 2.0f * M_PI); // Random twinkle phase
std::uniform_real_distribution<float> phiDist(0.0f, M_PI / 2.0f); // 090° (upper hemisphere)
std::uniform_real_distribution<float> thetaDist(0.0f, 2.0f * M_PI); // 0360°
std::uniform_real_distribution<float> brightnessDist(0.3f, 1.0f);
std::uniform_real_distribution<float> twinkleDist(0.0f, 2.0f * M_PI);
const float radius = 900.0f; // Slightly larger than skybox
for (int i = 0; i < starCount; i++) {
Star star;
// Spherical coordinates (hemisphere)
float phi = phiDist(gen); // Elevation angle
float phi = phiDist(gen); // Elevation angle
float theta = thetaDist(gen); // Azimuth angle
// Convert to Cartesian coordinates
float x = radius * std::sin(phi) * std::cos(theta);
float y = radius * std::sin(phi) * std::sin(theta);
float z = radius * std::cos(phi);
star.position = glm::vec3(x, y, z);
star.brightness = brightnessDist(gen);
star.position = glm::vec3(x, y, z);
star.brightness = brightnessDist(gen);
star.twinklePhase = twinkleDist(gen);
stars.push_back(star);
@ -190,9 +206,9 @@ void StarField::generateStars() {
}
void StarField::createStarBuffers() {
// Prepare vertex data (position, brightness, twinkle phase)
// Interleaved vertex data: pos.x, pos.y, pos.z, brightness, twinklePhase
std::vector<float> vertexData;
vertexData.reserve(stars.size() * 5); // 3 pos + 1 brightness + 1 phase
vertexData.reserve(stars.size() * 5);
for (const auto& star : stars) {
vertexData.push_back(star.position.x);
@ -202,57 +218,36 @@ void StarField::createStarBuffers() {
vertexData.push_back(star.twinklePhase);
}
// Create OpenGL buffers
glGenVertexArrays(1, &vao);
glGenBuffers(1, &vbo);
VkDeviceSize bufferSize = vertexData.size() * sizeof(float);
glBindVertexArray(vao);
// Upload via staging buffer to GPU-local memory
AllocatedBuffer gpuBuf = uploadBuffer(*vkCtx, vertexData.data(), bufferSize,
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
// Upload vertex data
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, vertexData.size() * sizeof(float), vertexData.data(), GL_STATIC_DRAW);
// Set vertex attributes
// Position
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// Brightness
glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
// Twinkle phase
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(4 * sizeof(float)));
glEnableVertexAttribArray(2);
glBindVertexArray(0);
vertexBuffer = gpuBuf.buffer;
vertexAlloc = gpuBuf.allocation;
}
void StarField::destroyStarBuffers() {
if (vao != 0) {
glDeleteVertexArrays(1, &vao);
vao = 0;
}
if (vbo != 0) {
glDeleteBuffers(1, &vbo);
vbo = 0;
if (vkCtx && vertexBuffer != VK_NULL_HANDLE) {
vmaDestroyBuffer(vkCtx->getAllocator(), vertexBuffer, vertexAlloc);
vertexBuffer = VK_NULL_HANDLE;
vertexAlloc = VK_NULL_HANDLE;
}
}
float StarField::getStarIntensity(float timeOfDay) const {
// Stars visible at night (fade in/out at dusk/dawn)
// Full night: 20:00-4:00
// Full night: 20:004:00
if (timeOfDay >= 20.0f || timeOfDay < 4.0f) {
return 1.0f;
}
// Fade in at dusk: 18:00-20:00
// Fade in at dusk: 18:0020:00
else if (timeOfDay >= 18.0f && timeOfDay < 20.0f) {
return (timeOfDay - 18.0f) / 2.0f; // 0 to 1 over 2 hours
return (timeOfDay - 18.0f) / 2.0f; // 0 1 over 2 hours
}
// Fade out at dawn: 4:00-6:00
// Fade out at dawn: 4:006:00
else if (timeOfDay >= 4.0f && timeOfDay < 6.0f) {
return 1.0f - (timeOfDay - 4.0f) / 2.0f; // 1 to 0 over 2 hours
return 1.0f - (timeOfDay - 4.0f) / 2.0f; // 1 0 over 2 hours
}
// Daytime: no stars
else {