mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Add configurable MSAA anti-aliasing, update auth screen and terrain shader
- MSAA: conditional 2-att (off) vs 3-att (on) render pass with auto-resolve - MSAA: multisampled color+depth images, query max supported sample count - MSAA: .setMultisample() on all 25+ main-pass pipelines across 17 renderers - MSAA: recreatePipelines() on every sub-renderer for runtime MSAA changes - MSAA: Renderer::setMsaaSamples() orchestrates swapchain+pipeline+ImGui rebuild - MSAA: Anti-Aliasing combo (Off/2x/4x/8x) in Video settings, persisted - Update auth screen assets and terrain fragment shader
This commit is contained in:
parent
6d213ad49b
commit
e12141a673
54 changed files with 2069 additions and 144 deletions
|
|
@ -522,9 +522,14 @@ if(NOT MSVC)
|
|||
target_compile_options(wowee PRIVATE $<$<CONFIG:Release>:-fvisibility=hidden -fvisibility-inlines-hidden>)
|
||||
endif()
|
||||
|
||||
# Copy assets to build directory
|
||||
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/assets
|
||||
DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
||||
# Copy assets to build directory (runs every build, not just configure)
|
||||
add_custom_target(copy_assets ALL
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/assets
|
||||
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/assets
|
||||
COMMENT "Syncing assets to build directory"
|
||||
)
|
||||
add_dependencies(wowee copy_assets)
|
||||
|
||||
# Install targets
|
||||
install(TARGETS wowee
|
||||
|
|
|
|||
BIN
assets/krayonload.png
Normal file
BIN
assets/krayonload.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
BIN
assets/krayonsignin.png
Normal file
BIN
assets/krayonsignin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 135 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 142 KiB |
|
|
@ -55,18 +55,22 @@ float sampleAlpha(sampler2D tex, vec2 uv) {
|
|||
|
||||
void main() {
|
||||
vec4 baseColor = texture(uBaseTexture, TexCoord);
|
||||
float a1 = hasLayer1 != 0 ? sampleAlpha(uLayer1Alpha, LayerUV) : 0.0;
|
||||
float a2 = hasLayer2 != 0 ? sampleAlpha(uLayer2Alpha, LayerUV) : 0.0;
|
||||
float a3 = hasLayer3 != 0 ? sampleAlpha(uLayer3Alpha, LayerUV) : 0.0;
|
||||
|
||||
float w0 = 1.0, w1 = a1, w2 = a2, w3 = a3;
|
||||
float sum = w0 + w1 + w2 + w3;
|
||||
if (sum > 0.0) { w0 /= sum; w1 /= sum; w2 /= sum; w3 /= sum; }
|
||||
|
||||
vec4 finalColor = baseColor * w0;
|
||||
if (hasLayer1 != 0) finalColor += texture(uLayer1Texture, TexCoord) * w1;
|
||||
if (hasLayer2 != 0) finalColor += texture(uLayer2Texture, TexCoord) * w2;
|
||||
if (hasLayer3 != 0) finalColor += texture(uLayer3Texture, TexCoord) * w3;
|
||||
// WoW terrain: layers are blended sequentially, each on top of the previous result.
|
||||
// Alpha=1 means the layer fully covers everything below; alpha=0 means invisible.
|
||||
vec4 finalColor = baseColor;
|
||||
if (hasLayer1 != 0) {
|
||||
float a1 = sampleAlpha(uLayer1Alpha, LayerUV);
|
||||
finalColor = mix(finalColor, texture(uLayer1Texture, TexCoord), a1);
|
||||
}
|
||||
if (hasLayer2 != 0) {
|
||||
float a2 = sampleAlpha(uLayer2Alpha, LayerUV);
|
||||
finalColor = mix(finalColor, texture(uLayer2Texture, TexCoord), a2);
|
||||
}
|
||||
if (hasLayer3 != 0) {
|
||||
float a3 = sampleAlpha(uLayer3Alpha, LayerUV);
|
||||
finalColor = mix(finalColor, texture(uLayer3Texture, TexCoord), a3);
|
||||
}
|
||||
|
||||
vec3 norm = normalize(Normal);
|
||||
vec3 lightDir2 = normalize(-lightDir.xyz);
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -32,6 +32,7 @@ public:
|
|||
*/
|
||||
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout);
|
||||
void shutdown();
|
||||
void recreatePipelines();
|
||||
|
||||
/**
|
||||
* Render celestial bodies (sun and moons).
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ public:
|
|||
void update(float deltaTime, const glm::vec3& cameraPos = glm::vec3(0.0f));
|
||||
|
||||
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const Camera& camera);
|
||||
void recreatePipelines();
|
||||
bool initializeShadow(VkRenderPass shadowRenderPass);
|
||||
void renderShadow(VkCommandBuffer cmd, const glm::mat4& lightSpaceMatrix);
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ public:
|
|||
|
||||
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout);
|
||||
void shutdown();
|
||||
void recreatePipelines();
|
||||
|
||||
/// Try to load M2 spell models (Charge_Caster.m2, etc.)
|
||||
void tryLoadM2Models(M2Renderer* m2Renderer, pipeline::AssetManager* assets);
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ public:
|
|||
*/
|
||||
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout);
|
||||
void shutdown();
|
||||
void recreatePipelines();
|
||||
|
||||
/**
|
||||
* Render clouds.
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ public:
|
|||
*/
|
||||
void shutdown();
|
||||
|
||||
void recreatePipelines();
|
||||
|
||||
/**
|
||||
* @brief Render lens flare effect
|
||||
* @param cmd Command buffer to record into
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ public:
|
|||
|
||||
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout);
|
||||
void shutdown();
|
||||
void recreatePipelines();
|
||||
|
||||
void update(float deltaTime, const Camera& camera);
|
||||
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet);
|
||||
|
|
|
|||
|
|
@ -281,6 +281,8 @@ public:
|
|||
double getQueryTimeMs() const { return queryTimeMs; }
|
||||
uint32_t getQueryCallCount() const { return queryCallCount; }
|
||||
|
||||
void recreatePipelines();
|
||||
|
||||
// Stats
|
||||
bool isInitialized() const { return initialized_; }
|
||||
uint32_t getModelCount() const { return static_cast<uint32_t>(models.size()); }
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ public:
|
|||
|
||||
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout);
|
||||
void shutdown();
|
||||
void recreatePipelines();
|
||||
|
||||
// Spawn dust particles at mount feet when moving on ground
|
||||
void spawnDust(const glm::vec3& position, const glm::vec3& velocity, bool isMoving);
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ public:
|
|||
|
||||
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout, pipeline::AssetManager* assetManager);
|
||||
void shutdown();
|
||||
void recreatePipelines();
|
||||
|
||||
/**
|
||||
* Add or update a quest marker at a position
|
||||
|
|
|
|||
|
|
@ -241,6 +241,7 @@ private:
|
|||
public:
|
||||
void setShadowsEnabled(bool enabled) { shadowsEnabled = enabled; }
|
||||
bool areShadowsEnabled() const { return shadowsEnabled; }
|
||||
void setMsaaSamples(VkSampleCountFlagBits samples);
|
||||
|
||||
private:
|
||||
void renderShadowPass();
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ public:
|
|||
|
||||
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout);
|
||||
void shutdown();
|
||||
void recreatePipelines();
|
||||
|
||||
/**
|
||||
* Render the skybox
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ public:
|
|||
|
||||
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout);
|
||||
void shutdown();
|
||||
void recreatePipelines();
|
||||
|
||||
/**
|
||||
* Render the star field
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ public:
|
|||
|
||||
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout);
|
||||
void shutdown();
|
||||
void recreatePipelines();
|
||||
void update(const Camera& camera, const CameraController& cc,
|
||||
const WaterRenderer& water, float deltaTime);
|
||||
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet);
|
||||
|
|
|
|||
|
|
@ -104,6 +104,8 @@ public:
|
|||
|
||||
void clear();
|
||||
|
||||
void recreatePipelines();
|
||||
|
||||
void setWireframe(bool enabled) { wireframe = enabled; }
|
||||
void setFrustumCulling(bool enabled) { frustumCullingEnabled = enabled; }
|
||||
void setFogEnabled(bool enabled) { fogEnabled = enabled; }
|
||||
|
|
@ -139,7 +141,7 @@ private:
|
|||
|
||||
// Descriptor pool for material sets
|
||||
VkDescriptorPool materialDescPool = VK_NULL_HANDLE;
|
||||
static constexpr uint32_t MAX_MATERIAL_SETS = 8192;
|
||||
static constexpr uint32_t MAX_MATERIAL_SETS = 16384;
|
||||
|
||||
// Loaded terrain chunks
|
||||
std::vector<TerrainChunkGPU> chunks;
|
||||
|
|
|
|||
|
|
@ -71,6 +71,11 @@ public:
|
|||
|
||||
bool isSwapchainDirty() const { return swapchainDirty; }
|
||||
|
||||
// MSAA
|
||||
VkSampleCountFlagBits getMsaaSamples() const { return msaaSamples_; }
|
||||
void setMsaaSamples(VkSampleCountFlagBits samples);
|
||||
VkSampleCountFlagBits getMaxUsableSampleCount() const;
|
||||
|
||||
private:
|
||||
bool createInstance(SDL_Window* window);
|
||||
bool createSurface(SDL_Window* window);
|
||||
|
|
@ -126,6 +131,15 @@ private:
|
|||
bool createDepthBuffer();
|
||||
void destroyDepthBuffer();
|
||||
|
||||
// MSAA resources
|
||||
VkSampleCountFlagBits msaaSamples_ = VK_SAMPLE_COUNT_1_BIT;
|
||||
VkImage msaaColorImage_ = VK_NULL_HANDLE;
|
||||
VkImageView msaaColorView_ = VK_NULL_HANDLE;
|
||||
VmaAllocation msaaColorAllocation_ = VK_NULL_HANDLE;
|
||||
|
||||
bool createMsaaColorImage();
|
||||
void destroyMsaaColorImage();
|
||||
|
||||
// ImGui resources
|
||||
VkRenderPass imguiRenderPass = VK_NULL_HANDLE;
|
||||
VkDescriptorPool imguiDescriptorPool = VK_NULL_HANDLE;
|
||||
|
|
|
|||
|
|
@ -79,6 +79,8 @@ public:
|
|||
void removeTile(int tileX, int tileY);
|
||||
void clear();
|
||||
|
||||
void recreatePipelines();
|
||||
|
||||
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const Camera& camera, float time);
|
||||
|
||||
void setEnabled(bool enabled) { renderingEnabled = enabled; }
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ public:
|
|||
* @return true if initialization succeeded
|
||||
*/
|
||||
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout);
|
||||
void recreatePipelines();
|
||||
|
||||
/**
|
||||
* @brief Update weather particles
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ public:
|
|||
/**
|
||||
* Get number of loaded models
|
||||
*/
|
||||
void recreatePipelines();
|
||||
bool isInitialized() const { return initialized_; }
|
||||
uint32_t getModelCount() const { return loadedModels.size(); }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include "auth/auth_handler.hpp"
|
||||
#include "rendering/video_player.hpp"
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
namespace wowee { namespace rendering { class VkContext; } }
|
||||
|
||||
namespace wowee { namespace ui {
|
||||
|
||||
/**
|
||||
|
|
@ -103,9 +105,18 @@ private:
|
|||
void upsertCurrentServerProfile(bool includePasswordHash);
|
||||
std::string currentExpansionId() const;
|
||||
|
||||
// Background video
|
||||
bool videoInitAttempted = false;
|
||||
rendering::VideoPlayer backgroundVideo;
|
||||
// Background image (Vulkan)
|
||||
bool bgInitAttempted = false;
|
||||
bool loadBackgroundImage();
|
||||
void destroyBackgroundImage();
|
||||
rendering::VkContext* bgVkCtx = nullptr;
|
||||
VkImage bgImage = VK_NULL_HANDLE;
|
||||
VkDeviceMemory bgMemory = VK_NULL_HANDLE;
|
||||
VkImageView bgImageView = VK_NULL_HANDLE;
|
||||
VkSampler bgSampler = VK_NULL_HANDLE;
|
||||
VkDescriptorSet bgDescriptorSet = VK_NULL_HANDLE;
|
||||
int bgWidth = 0;
|
||||
int bgHeight = 0;
|
||||
|
||||
bool musicInitAttempted = false;
|
||||
bool musicPlaying = false;
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ private:
|
|||
bool pendingAutoLoot = false;
|
||||
bool pendingUseOriginalSoundtrack = true;
|
||||
int pendingGroundClutterDensity = 100;
|
||||
int pendingAntiAliasing = 0; // 0=Off, 1=2x, 2=4x, 3=8x
|
||||
|
||||
// UI element transparency (0.0 = fully transparent, 1.0 = fully opaque)
|
||||
float uiOpacity_ = 0.65f;
|
||||
|
|
@ -110,6 +111,7 @@ private:
|
|||
bool minimapNpcDots_ = false;
|
||||
bool minimapSettingsApplied_ = false;
|
||||
bool volumeSettingsApplied_ = false; // True once saved volume settings applied to audio managers
|
||||
bool msaaSettingsApplied_ = false; // True once saved MSAA setting applied to renderer
|
||||
|
||||
// Mute state: mute bypasses master volume without touching slider values
|
||||
bool soundMuted_ = false;
|
||||
|
|
|
|||
|
|
@ -722,6 +722,15 @@ void GameHandler::update(float deltaTime) {
|
|||
? 0.25f
|
||||
: (classicLikeCombatSync ? 0.05f : moveHeartbeatInterval_);
|
||||
if (timeSinceLastMoveHeartbeat_ >= heartbeatInterval) {
|
||||
// Debug: log heartbeat position periodically
|
||||
static int hbCount = 0;
|
||||
if (++hbCount <= 5 || hbCount % 60 == 0) {
|
||||
glm::vec3 serverPos = core::coords::canonicalToServer(
|
||||
glm::vec3(movementInfo.x, movementInfo.y, movementInfo.z));
|
||||
LOG_INFO("Heartbeat #", hbCount, " canonical=(",
|
||||
movementInfo.x, ",", movementInfo.y, ",", movementInfo.z,
|
||||
") server=(", serverPos.x, ",", serverPos.y, ",", serverPos.z, ")");
|
||||
}
|
||||
sendMovement(Opcode::MSG_MOVE_HEARTBEAT);
|
||||
timeSinceLastMoveHeartbeat_ = 0.0f;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -328,10 +328,14 @@ void ADTLoader::parseMCNK(const uint8_t* data, size_t size, int chunkIndex, ADTT
|
|||
" holes=0x", std::hex, chunk.holes, std::dec);
|
||||
}
|
||||
|
||||
// Position (stored at offset 0x68 = 104 in MCNK header)
|
||||
chunk.position[0] = readFloat(data, 104); // X
|
||||
chunk.position[1] = readFloat(data, 108); // Y
|
||||
chunk.position[2] = readFloat(data, 112); // Z
|
||||
// MCNK position is in canonical WoW coordinates (NOT ADT placement space):
|
||||
// offset 104: wowY (west axis, horizontal — unused, XY computed from tile indices)
|
||||
// offset 108: wowX (north axis, horizontal — unused, XY computed from tile indices)
|
||||
// offset 112: wowZ = HEIGHT BASE (MCVT heights are relative to this)
|
||||
chunk.position[0] = readFloat(data, 104); // wowY (unused)
|
||||
chunk.position[1] = readFloat(data, 108); // wowX (unused)
|
||||
chunk.position[2] = readFloat(data, 112); // wowZ = height base
|
||||
|
||||
|
||||
// Parse sub-chunks using offsets from MCNK header
|
||||
// WoW ADT sub-chunks may have their own 8-byte headers (magic+size)
|
||||
|
|
@ -409,7 +413,11 @@ void ADTLoader::parseMCVT(const uint8_t* data, size_t size, MapChunk& chunk) {
|
|||
// Log height range for first chunk only
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOG_DEBUG("MCVT height range: [", minHeight, ", ", maxHeight, "]");
|
||||
LOG_INFO("MCVT height range: [", minHeight, ", ", maxHeight, "]",
|
||||
" (heights[0]=", chunk.heightMap.heights[0],
|
||||
" heights[8]=", chunk.heightMap.heights[8],
|
||||
" heights[136]=", chunk.heightMap.heights[136],
|
||||
" heights[144]=", chunk.heightMap.heights[144], ")");
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "pipeline/terrain_mesh.hpp"
|
||||
#include "core/coordinates.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include <cmath>
|
||||
|
||||
|
|
@ -40,6 +41,7 @@ TerrainMesh TerrainMeshGenerator::generate(const ADTTerrain& terrain) {
|
|||
|
||||
mesh.validChunkCount = validCount;
|
||||
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
|
|
@ -49,10 +51,24 @@ ChunkMesh TerrainMeshGenerator::generateChunkMesh(const MapChunk& chunk, int chu
|
|||
mesh.chunkX = chunkX;
|
||||
mesh.chunkY = chunkY;
|
||||
|
||||
// World position from chunk data
|
||||
mesh.worldX = chunk.position[0];
|
||||
mesh.worldY = chunk.position[1];
|
||||
mesh.worldZ = chunk.position[2];
|
||||
// Compute render-space XY from tile/chunk indices (MCNK position fields are unreliable).
|
||||
// tileX increases southward (renderY axis), tileY increases eastward (renderX axis).
|
||||
// NW corner of tile: renderX = (32-tileY)*TILE_SIZE, renderY = (32-tileX)*TILE_SIZE
|
||||
// Each chunk step goes east (–renderX) or south (–renderY).
|
||||
const float tileNW_renderX = (32.0f - static_cast<float>(tileY)) * core::coords::TILE_SIZE;
|
||||
const float tileNW_renderY = (32.0f - static_cast<float>(tileX)) * core::coords::TILE_SIZE;
|
||||
mesh.worldX = tileNW_renderX - static_cast<float>(chunkY) * CHUNK_SIZE; // iy controls renderX (east-west)
|
||||
mesh.worldY = tileNW_renderY - static_cast<float>(chunkX) * CHUNK_SIZE; // ix controls renderY (north-south)
|
||||
mesh.worldZ = chunk.position[2]; // height base (wowZ) from MCNK offset 112
|
||||
|
||||
// Debug: log chunk positions for first tile
|
||||
static int posLogCount = 0;
|
||||
if (posLogCount < 5) {
|
||||
posLogCount++;
|
||||
LOG_INFO("Terrain chunk: tile(", tileX, ",", tileY, ") ix=", chunkX, " iy=", chunkY,
|
||||
" worldXY=(", mesh.worldX, ",", mesh.worldY, ",", mesh.worldZ, ")",
|
||||
" mcnk=(", chunk.position[0], ",", chunk.position[1], ",", chunk.position[2], ")");
|
||||
}
|
||||
|
||||
// Generate vertices from heightmap (pass chunk grid indices and tile coords)
|
||||
mesh.vertices = generateVertices(chunk, chunkX, chunkY, tileX, tileY);
|
||||
|
|
@ -167,19 +183,21 @@ ChunkMesh TerrainMeshGenerator::generateChunkMesh(const MapChunk& chunk, int chu
|
|||
return mesh;
|
||||
}
|
||||
|
||||
std::vector<TerrainVertex> TerrainMeshGenerator::generateVertices(const MapChunk& chunk, [[maybe_unused]] int chunkX, [[maybe_unused]] int chunkY, [[maybe_unused]] int tileX, [[maybe_unused]] int tileY) {
|
||||
std::vector<TerrainVertex> TerrainMeshGenerator::generateVertices(const MapChunk& chunk, int chunkX, int chunkY, int tileX, int tileY) {
|
||||
std::vector<TerrainVertex> vertices;
|
||||
vertices.reserve(145); // 145 vertices total
|
||||
|
||||
const HeightMap& heightMap = chunk.heightMap;
|
||||
|
||||
// WoW terrain uses 145 heights stored in a 9x17 row-major grid layout
|
||||
const float unitSize = CHUNK_SIZE / 8.0f; // 66.67 units per vertex step
|
||||
const float unitSize = CHUNK_SIZE / 8.0f; // 33.333/8 units per vertex step
|
||||
|
||||
// chunk.position contains world coordinates for this chunk's origin
|
||||
// Both X and Y are at world scale (no scaling needed)
|
||||
float chunkBaseX = chunk.position[0];
|
||||
float chunkBaseY = chunk.position[1];
|
||||
// Compute render-space base from tile/chunk indices (same formula as generateChunkMesh).
|
||||
const float tileNW_renderX = (32.0f - static_cast<float>(tileY)) * core::coords::TILE_SIZE;
|
||||
const float tileNW_renderY = (32.0f - static_cast<float>(tileX)) * core::coords::TILE_SIZE;
|
||||
float chunkBaseX = tileNW_renderX - static_cast<float>(chunkY) * CHUNK_SIZE; // iy controls renderX (east-west)
|
||||
float chunkBaseY = tileNW_renderY - static_cast<float>(chunkX) * CHUNK_SIZE; // ix controls renderY (north-south)
|
||||
float chunkBaseZ = chunk.position[2]; // height base (wowZ) from MCNK offset 112
|
||||
|
||||
for (int index = 0; index < 145; index++) {
|
||||
int y = index / 17; // Row (0-8)
|
||||
|
|
@ -196,11 +214,12 @@ std::vector<TerrainVertex> TerrainMeshGenerator::generateVertices(const MapChunk
|
|||
|
||||
TerrainVertex vertex;
|
||||
|
||||
// Position - match wowee.js coordinate layout (swap X/Y and negate)
|
||||
// wowee.js: X = -(y * unitSize), Y = -(x * unitSize)
|
||||
vertex.position[0] = chunkBaseX - (offsetY * unitSize);
|
||||
vertex.position[1] = chunkBaseY - (offsetX * unitSize);
|
||||
vertex.position[2] = chunk.position[2] + heightMap.heights[index];
|
||||
// Position in render space:
|
||||
// MCVT rows (offsetY) go west→east = renderX decreasing
|
||||
// MCVT columns (offsetX) go north→south = renderY decreasing
|
||||
vertex.position[0] = chunkBaseX - (offsetY * unitSize); // renderX (row = west→east)
|
||||
vertex.position[1] = chunkBaseY - (offsetX * unitSize); // renderY (col = north→south)
|
||||
vertex.position[2] = chunkBaseZ + heightMap.heights[index]; // renderZ
|
||||
|
||||
// Normal
|
||||
if (index * 3 + 2 < static_cast<int>(chunk.normals.size())) {
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ bool Celestial::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout)
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL) // test on, write off (sky layer)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(vkCtx_->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -106,6 +107,71 @@ bool Celestial::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout)
|
|||
return true;
|
||||
}
|
||||
|
||||
void Celestial::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/celestial.vert.spv")) {
|
||||
LOG_ERROR("Celestial::recreatePipelines: failed to load vertex shader");
|
||||
return;
|
||||
}
|
||||
VkShaderModule fragModule;
|
||||
if (!fragModule.loadFromFile(device, "assets/shaders/celestial.frag.spv")) {
|
||||
LOG_ERROR("Celestial::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);
|
||||
|
||||
// Vertex input (same as initialize)
|
||||
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 uvAttr{};
|
||||
uvAttr.location = 1;
|
||||
uvAttr.binding = 0;
|
||||
uvAttr.format = VK_FORMAT_R32G32_SFLOAT;
|
||||
uvAttr.offset = 3 * sizeof(float);
|
||||
|
||||
std::vector<VkDynamicState> dynamicStates = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR
|
||||
};
|
||||
|
||||
pipeline_ = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, {posAttr, uvAttr})
|
||||
.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::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(vkCtx_->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
|
||||
if (pipeline_ == VK_NULL_HANDLE) {
|
||||
LOG_ERROR("Celestial::recreatePipelines: failed to create pipeline");
|
||||
}
|
||||
}
|
||||
|
||||
void Celestial::shutdown() {
|
||||
destroyQuad();
|
||||
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@ bool CharacterRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFram
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, depthWrite, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(blendState)
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
||||
|
|
@ -2564,5 +2565,69 @@ void CharacterRenderer::dumpAnimations(uint32_t instanceId) const {
|
|||
core::Logger::getInstance().info("=== End animation dump ===");
|
||||
}
|
||||
|
||||
void CharacterRenderer::recreatePipelines() {
|
||||
if (!vkCtx_) return;
|
||||
VkDevice device = vkCtx_->getDevice();
|
||||
vkDeviceWaitIdle(device);
|
||||
|
||||
// Destroy old main-pass pipelines (NOT shadow, NOT pipeline layout)
|
||||
if (opaquePipeline_) { vkDestroyPipeline(device, opaquePipeline_, nullptr); opaquePipeline_ = VK_NULL_HANDLE; }
|
||||
if (alphaTestPipeline_) { vkDestroyPipeline(device, alphaTestPipeline_, nullptr); alphaTestPipeline_ = VK_NULL_HANDLE; }
|
||||
if (alphaPipeline_) { vkDestroyPipeline(device, alphaPipeline_, nullptr); alphaPipeline_ = VK_NULL_HANDLE; }
|
||||
if (additivePipeline_) { vkDestroyPipeline(device, additivePipeline_, nullptr); additivePipeline_ = VK_NULL_HANDLE; }
|
||||
|
||||
// --- Load shaders ---
|
||||
rendering::VkShaderModule charVert, charFrag;
|
||||
charVert.loadFromFile(device, "assets/shaders/character.vert.spv");
|
||||
charFrag.loadFromFile(device, "assets/shaders/character.frag.spv");
|
||||
|
||||
if (!charVert.isValid() || !charFrag.isValid()) {
|
||||
LOG_ERROR("CharacterRenderer::recreatePipelines: missing required shaders");
|
||||
return;
|
||||
}
|
||||
|
||||
VkRenderPass mainPass = vkCtx_->getImGuiRenderPass();
|
||||
|
||||
// --- Vertex input ---
|
||||
VkVertexInputBindingDescription charBinding{};
|
||||
charBinding.binding = 0;
|
||||
charBinding.stride = sizeof(pipeline::M2Vertex);
|
||||
charBinding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
std::vector<VkVertexInputAttributeDescription> charAttrs = {
|
||||
{0, 0, VK_FORMAT_R32G32B32_SFLOAT, static_cast<uint32_t>(offsetof(pipeline::M2Vertex, position))},
|
||||
{1, 0, VK_FORMAT_R8G8B8A8_UNORM, static_cast<uint32_t>(offsetof(pipeline::M2Vertex, boneWeights))},
|
||||
{2, 0, VK_FORMAT_R8G8B8A8_UINT, static_cast<uint32_t>(offsetof(pipeline::M2Vertex, boneIndices))},
|
||||
{3, 0, VK_FORMAT_R32G32B32_SFLOAT, static_cast<uint32_t>(offsetof(pipeline::M2Vertex, normal))},
|
||||
{4, 0, VK_FORMAT_R32G32_SFLOAT, static_cast<uint32_t>(offsetof(pipeline::M2Vertex, texCoords))},
|
||||
};
|
||||
|
||||
auto buildCharPipeline = [&](VkPipelineColorBlendAttachmentState blendState, bool depthWrite) -> VkPipeline {
|
||||
return PipelineBuilder()
|
||||
.setShaders(charVert.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
charFrag.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||
.setVertexInput({charBinding}, charAttrs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, depthWrite, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(blendState)
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
||||
.build(device);
|
||||
};
|
||||
|
||||
opaquePipeline_ = buildCharPipeline(PipelineBuilder::blendDisabled(), true);
|
||||
alphaTestPipeline_ = buildCharPipeline(PipelineBuilder::blendAlpha(), true);
|
||||
alphaPipeline_ = buildCharPipeline(PipelineBuilder::blendAlpha(), false);
|
||||
additivePipeline_ = buildCharPipeline(PipelineBuilder::blendAdditive(), false);
|
||||
|
||||
charVert.destroy();
|
||||
charFrag.destroy();
|
||||
|
||||
core::Logger::getInstance().info("CharacterRenderer: pipelines recreated");
|
||||
}
|
||||
|
||||
} // namespace rendering
|
||||
} // namespace wowee
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ bool ChargeEffect::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayo
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAdditive()) // Additive blend for fiery glow
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(ribbonPipelineLayout_)
|
||||
.setRenderPass(vkCtx_->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -160,6 +161,7 @@ bool ChargeEffect::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayo
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(dustPipelineLayout_)
|
||||
.setRenderPass(vkCtx_->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -249,6 +251,122 @@ void ChargeEffect::shutdown() {
|
|||
dustPuffs_.clear();
|
||||
}
|
||||
|
||||
void ChargeEffect::recreatePipelines() {
|
||||
if (!vkCtx_) return;
|
||||
VkDevice device = vkCtx_->getDevice();
|
||||
|
||||
// Destroy old pipelines (NOT layouts)
|
||||
if (ribbonPipeline_ != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device, ribbonPipeline_, nullptr);
|
||||
ribbonPipeline_ = VK_NULL_HANDLE;
|
||||
}
|
||||
if (dustPipeline_ != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device, dustPipeline_, nullptr);
|
||||
dustPipeline_ = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
std::vector<VkDynamicState> dynamicStates = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR
|
||||
};
|
||||
|
||||
// ---- Rebuild ribbon trail pipeline (TRIANGLE_STRIP) ----
|
||||
{
|
||||
VkShaderModule vertModule;
|
||||
vertModule.loadFromFile(device, "assets/shaders/charge_ribbon.vert.spv");
|
||||
VkShaderModule fragModule;
|
||||
fragModule.loadFromFile(device, "assets/shaders/charge_ribbon.frag.spv");
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
VkVertexInputBindingDescription binding{};
|
||||
binding.binding = 0;
|
||||
binding.stride = 6 * sizeof(float);
|
||||
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
std::vector<VkVertexInputAttributeDescription> attrs(4);
|
||||
attrs[0].location = 0;
|
||||
attrs[0].binding = 0;
|
||||
attrs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
|
||||
attrs[0].offset = 0;
|
||||
attrs[1].location = 1;
|
||||
attrs[1].binding = 0;
|
||||
attrs[1].format = VK_FORMAT_R32_SFLOAT;
|
||||
attrs[1].offset = 3 * sizeof(float);
|
||||
attrs[2].location = 2;
|
||||
attrs[2].binding = 0;
|
||||
attrs[2].format = VK_FORMAT_R32_SFLOAT;
|
||||
attrs[2].offset = 4 * sizeof(float);
|
||||
attrs[3].location = 3;
|
||||
attrs[3].binding = 0;
|
||||
attrs[3].format = VK_FORMAT_R32_SFLOAT;
|
||||
attrs[3].offset = 5 * sizeof(float);
|
||||
|
||||
ribbonPipeline_ = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, attrs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAdditive())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(ribbonPipelineLayout_)
|
||||
.setRenderPass(vkCtx_->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
}
|
||||
|
||||
// ---- Rebuild dust puff pipeline (POINT_LIST) ----
|
||||
{
|
||||
VkShaderModule vertModule;
|
||||
vertModule.loadFromFile(device, "assets/shaders/charge_dust.vert.spv");
|
||||
VkShaderModule fragModule;
|
||||
fragModule.loadFromFile(device, "assets/shaders/charge_dust.frag.spv");
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
VkVertexInputBindingDescription binding{};
|
||||
binding.binding = 0;
|
||||
binding.stride = 5 * sizeof(float);
|
||||
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
std::vector<VkVertexInputAttributeDescription> attrs(3);
|
||||
attrs[0].location = 0;
|
||||
attrs[0].binding = 0;
|
||||
attrs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
|
||||
attrs[0].offset = 0;
|
||||
attrs[1].location = 1;
|
||||
attrs[1].binding = 0;
|
||||
attrs[1].format = VK_FORMAT_R32_SFLOAT;
|
||||
attrs[1].offset = 3 * sizeof(float);
|
||||
attrs[2].location = 2;
|
||||
attrs[2].binding = 0;
|
||||
attrs[2].format = VK_FORMAT_R32_SFLOAT;
|
||||
attrs[2].offset = 4 * sizeof(float);
|
||||
|
||||
dustPipeline_ = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, attrs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_POINT_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(dustPipelineLayout_)
|
||||
.setRenderPass(vkCtx_->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void ChargeEffect::tryLoadM2Models(M2Renderer* m2Renderer, pipeline::AssetManager* assets) {
|
||||
if (!m2Renderer || !assets) return;
|
||||
m2Renderer_ = m2Renderer;
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ bool Clouds::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) {
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL) // test on, write off (sky layer)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(vkCtx_->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -100,6 +101,65 @@ bool Clouds::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void Clouds::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/clouds.vert.spv")) {
|
||||
LOG_ERROR("Clouds::recreatePipelines: failed to load vertex shader");
|
||||
return;
|
||||
}
|
||||
VkShaderModule fragModule;
|
||||
if (!fragModule.loadFromFile(device, "assets/shaders/clouds.frag.spv")) {
|
||||
LOG_ERROR("Clouds::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);
|
||||
|
||||
// Vertex input (same as initialize)
|
||||
VkVertexInputBindingDescription binding{};
|
||||
binding.binding = 0;
|
||||
binding.stride = sizeof(glm::vec3);
|
||||
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
VkVertexInputAttributeDescription posAttr{};
|
||||
posAttr.location = 0;
|
||||
posAttr.binding = 0;
|
||||
posAttr.format = VK_FORMAT_R32G32B32_SFLOAT;
|
||||
posAttr.offset = 0;
|
||||
|
||||
std::vector<VkDynamicState> dynamicStates = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR
|
||||
};
|
||||
|
||||
pipeline_ = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, {posAttr})
|
||||
.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::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(vkCtx_->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
|
||||
if (pipeline_ == VK_NULL_HANDLE) {
|
||||
LOG_ERROR("Clouds::recreatePipelines: failed to create pipeline");
|
||||
}
|
||||
}
|
||||
|
||||
void Clouds::shutdown() {
|
||||
destroyBuffers();
|
||||
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ bool LensFlare::initialize(VkContext* ctx, VkDescriptorSetLayout /*perFrameLayou
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setNoDepthTest()
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAdditive())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -146,6 +147,63 @@ void LensFlare::shutdown() {
|
|||
vkCtx = nullptr;
|
||||
}
|
||||
|
||||
void LensFlare::recreatePipelines() {
|
||||
if (!vkCtx) return;
|
||||
VkDevice device = vkCtx->getDevice();
|
||||
|
||||
// Destroy old pipeline (NOT layout)
|
||||
if (pipeline != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device, pipeline, nullptr);
|
||||
pipeline = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkShaderModule vertModule;
|
||||
vertModule.loadFromFile(device, "assets/shaders/lens_flare.vert.spv");
|
||||
VkShaderModule fragModule;
|
||||
fragModule.loadFromFile(device, "assets/shaders/lens_flare.frag.spv");
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
VkVertexInputBindingDescription binding{};
|
||||
binding.binding = 0;
|
||||
binding.stride = 4 * sizeof(float);
|
||||
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
VkVertexInputAttributeDescription posAttr{};
|
||||
posAttr.location = 0;
|
||||
posAttr.binding = 0;
|
||||
posAttr.format = VK_FORMAT_R32G32_SFLOAT;
|
||||
posAttr.offset = 0;
|
||||
|
||||
VkVertexInputAttributeDescription uvAttr{};
|
||||
uvAttr.location = 1;
|
||||
uvAttr.binding = 0;
|
||||
uvAttr.format = VK_FORMAT_R32G32_SFLOAT;
|
||||
uvAttr.offset = 2 * sizeof(float);
|
||||
|
||||
std::vector<VkDynamicState> dynamicStates = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR
|
||||
};
|
||||
|
||||
pipeline = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, {posAttr, uvAttr})
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setNoDepthTest()
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAdditive())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
}
|
||||
|
||||
void LensFlare::generateFlareElements() {
|
||||
flareElements.clear();
|
||||
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ bool Lightning::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout)
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setNoDepthTest() // Always visible (like the GL version)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAdditive()) // Additive for electric glow
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(boltPipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -164,6 +165,7 @@ bool Lightning::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout)
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setNoDepthTest()
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(flashPipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -253,6 +255,102 @@ void Lightning::shutdown() {
|
|||
vkCtx = nullptr;
|
||||
}
|
||||
|
||||
void Lightning::recreatePipelines() {
|
||||
if (!vkCtx) return;
|
||||
VkDevice device = vkCtx->getDevice();
|
||||
|
||||
// Destroy old pipelines (NOT layouts)
|
||||
if (boltPipeline != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device, boltPipeline, nullptr);
|
||||
boltPipeline = VK_NULL_HANDLE;
|
||||
}
|
||||
if (flashPipeline != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device, flashPipeline, nullptr);
|
||||
flashPipeline = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
std::vector<VkDynamicState> dynamicStates = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR
|
||||
};
|
||||
|
||||
// ---- Rebuild bolt pipeline (LINE_STRIP) ----
|
||||
{
|
||||
VkShaderModule vertModule;
|
||||
vertModule.loadFromFile(device, "assets/shaders/lightning_bolt.vert.spv");
|
||||
VkShaderModule fragModule;
|
||||
fragModule.loadFromFile(device, "assets/shaders/lightning_bolt.frag.spv");
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
VkVertexInputBindingDescription binding{};
|
||||
binding.binding = 0;
|
||||
binding.stride = sizeof(glm::vec3);
|
||||
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
VkVertexInputAttributeDescription posAttr{};
|
||||
posAttr.location = 0;
|
||||
posAttr.binding = 0;
|
||||
posAttr.format = VK_FORMAT_R32G32B32_SFLOAT;
|
||||
posAttr.offset = 0;
|
||||
|
||||
boltPipeline = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, {posAttr})
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_LINE_STRIP)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setNoDepthTest()
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAdditive())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(boltPipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
}
|
||||
|
||||
// ---- Rebuild flash pipeline (TRIANGLE_STRIP) ----
|
||||
{
|
||||
VkShaderModule vertModule;
|
||||
vertModule.loadFromFile(device, "assets/shaders/lightning_flash.vert.spv");
|
||||
VkShaderModule fragModule;
|
||||
fragModule.loadFromFile(device, "assets/shaders/lightning_flash.frag.spv");
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
VkVertexInputBindingDescription binding{};
|
||||
binding.binding = 0;
|
||||
binding.stride = 2 * sizeof(float);
|
||||
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
VkVertexInputAttributeDescription posAttr{};
|
||||
posAttr.location = 0;
|
||||
posAttr.binding = 0;
|
||||
posAttr.format = VK_FORMAT_R32G32_SFLOAT;
|
||||
posAttr.offset = 0;
|
||||
|
||||
flashPipeline = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, {posAttr})
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setNoDepthTest()
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(flashPipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void Lightning::update(float deltaTime, const Camera& camera) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -16,8 +16,7 @@ namespace wowee {
|
|||
namespace rendering {
|
||||
|
||||
LoadingScreen::LoadingScreen() {
|
||||
imagePaths.push_back("assets/loading1.jpeg");
|
||||
imagePaths.push_back("assets/loading2.jpeg");
|
||||
imagePaths.push_back("assets/krayonload.png");
|
||||
}
|
||||
|
||||
LoadingScreen::~LoadingScreen() {
|
||||
|
|
|
|||
|
|
@ -468,6 +468,7 @@ bool M2Renderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, depthWrite, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(blendState)
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
||||
|
|
@ -502,6 +503,7 @@ bool M2Renderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(blend)
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(particlePipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
||||
|
|
@ -534,6 +536,7 @@ bool M2Renderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(smokePipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
||||
|
|
@ -3677,5 +3680,144 @@ float M2Renderer::raycastBoundingBoxes(const glm::vec3& origin, const glm::vec3&
|
|||
return closestHit;
|
||||
}
|
||||
|
||||
void M2Renderer::recreatePipelines() {
|
||||
if (!vkCtx_) return;
|
||||
VkDevice device = vkCtx_->getDevice();
|
||||
vkDeviceWaitIdle(device);
|
||||
|
||||
// Destroy old main-pass pipelines (NOT shadow, NOT pipeline layouts)
|
||||
if (opaquePipeline_) { vkDestroyPipeline(device, opaquePipeline_, nullptr); opaquePipeline_ = VK_NULL_HANDLE; }
|
||||
if (alphaTestPipeline_) { vkDestroyPipeline(device, alphaTestPipeline_, nullptr); alphaTestPipeline_ = VK_NULL_HANDLE; }
|
||||
if (alphaPipeline_) { vkDestroyPipeline(device, alphaPipeline_, nullptr); alphaPipeline_ = VK_NULL_HANDLE; }
|
||||
if (additivePipeline_) { vkDestroyPipeline(device, additivePipeline_, nullptr); additivePipeline_ = VK_NULL_HANDLE; }
|
||||
if (particlePipeline_) { vkDestroyPipeline(device, particlePipeline_, nullptr); particlePipeline_ = VK_NULL_HANDLE; }
|
||||
if (particleAdditivePipeline_) { vkDestroyPipeline(device, particleAdditivePipeline_, nullptr); particleAdditivePipeline_ = VK_NULL_HANDLE; }
|
||||
if (smokePipeline_) { vkDestroyPipeline(device, smokePipeline_, nullptr); smokePipeline_ = VK_NULL_HANDLE; }
|
||||
|
||||
// --- Load shaders ---
|
||||
rendering::VkShaderModule m2Vert, m2Frag;
|
||||
rendering::VkShaderModule particleVert, particleFrag;
|
||||
rendering::VkShaderModule smokeVert, smokeFrag;
|
||||
|
||||
m2Vert.loadFromFile(device, "assets/shaders/m2.vert.spv");
|
||||
m2Frag.loadFromFile(device, "assets/shaders/m2.frag.spv");
|
||||
particleVert.loadFromFile(device, "assets/shaders/m2_particle.vert.spv");
|
||||
particleFrag.loadFromFile(device, "assets/shaders/m2_particle.frag.spv");
|
||||
smokeVert.loadFromFile(device, "assets/shaders/m2_smoke.vert.spv");
|
||||
smokeFrag.loadFromFile(device, "assets/shaders/m2_smoke.frag.spv");
|
||||
|
||||
if (!m2Vert.isValid() || !m2Frag.isValid()) {
|
||||
LOG_ERROR("M2Renderer::recreatePipelines: missing required shaders");
|
||||
return;
|
||||
}
|
||||
|
||||
VkRenderPass mainPass = vkCtx_->getImGuiRenderPass();
|
||||
|
||||
// --- M2 model vertex input ---
|
||||
VkVertexInputBindingDescription m2Binding{};
|
||||
m2Binding.binding = 0;
|
||||
m2Binding.stride = 18 * sizeof(float);
|
||||
m2Binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
std::vector<VkVertexInputAttributeDescription> m2Attrs = {
|
||||
{0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0}, // position
|
||||
{1, 0, VK_FORMAT_R32G32B32_SFLOAT, 3 * sizeof(float)}, // normal
|
||||
{2, 0, VK_FORMAT_R32G32_SFLOAT, 6 * sizeof(float)}, // texCoord0
|
||||
{5, 0, VK_FORMAT_R32G32_SFLOAT, 8 * sizeof(float)}, // texCoord1
|
||||
{3, 0, VK_FORMAT_R32G32B32A32_SFLOAT, 10 * sizeof(float)}, // boneWeights
|
||||
{4, 0, VK_FORMAT_R32G32B32A32_SFLOAT, 14 * sizeof(float)}, // boneIndices (float)
|
||||
};
|
||||
|
||||
auto buildM2Pipeline = [&](VkPipelineColorBlendAttachmentState blendState, bool depthWrite) -> VkPipeline {
|
||||
return PipelineBuilder()
|
||||
.setShaders(m2Vert.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
m2Frag.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||
.setVertexInput({m2Binding}, m2Attrs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, depthWrite, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(blendState)
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
||||
.build(device);
|
||||
};
|
||||
|
||||
opaquePipeline_ = buildM2Pipeline(PipelineBuilder::blendDisabled(), true);
|
||||
alphaTestPipeline_ = buildM2Pipeline(PipelineBuilder::blendAlpha(), true);
|
||||
alphaPipeline_ = buildM2Pipeline(PipelineBuilder::blendAlpha(), false);
|
||||
additivePipeline_ = buildM2Pipeline(PipelineBuilder::blendAdditive(), false);
|
||||
|
||||
// --- Particle pipelines ---
|
||||
if (particleVert.isValid() && particleFrag.isValid()) {
|
||||
VkVertexInputBindingDescription pBind{};
|
||||
pBind.binding = 0;
|
||||
pBind.stride = 9 * sizeof(float); // pos3 + color4 + size1 + tile1
|
||||
pBind.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
std::vector<VkVertexInputAttributeDescription> pAttrs = {
|
||||
{0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0}, // position
|
||||
{1, 0, VK_FORMAT_R32G32B32A32_SFLOAT, 3 * sizeof(float)}, // color
|
||||
{2, 0, VK_FORMAT_R32_SFLOAT, 7 * sizeof(float)}, // size
|
||||
{3, 0, VK_FORMAT_R32_SFLOAT, 8 * sizeof(float)}, // tile
|
||||
};
|
||||
|
||||
auto buildParticlePipeline = [&](VkPipelineColorBlendAttachmentState blend) -> VkPipeline {
|
||||
return PipelineBuilder()
|
||||
.setShaders(particleVert.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
particleFrag.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||
.setVertexInput({pBind}, pAttrs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_POINT_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(blend)
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(particlePipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
||||
.build(device);
|
||||
};
|
||||
|
||||
particlePipeline_ = buildParticlePipeline(PipelineBuilder::blendAlpha());
|
||||
particleAdditivePipeline_ = buildParticlePipeline(PipelineBuilder::blendAdditive());
|
||||
}
|
||||
|
||||
// --- Smoke pipeline ---
|
||||
if (smokeVert.isValid() && smokeFrag.isValid()) {
|
||||
VkVertexInputBindingDescription sBind{};
|
||||
sBind.binding = 0;
|
||||
sBind.stride = 6 * sizeof(float); // pos3 + lifeRatio1 + size1 + isSpark1
|
||||
sBind.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
std::vector<VkVertexInputAttributeDescription> sAttrs = {
|
||||
{0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0}, // position
|
||||
{1, 0, VK_FORMAT_R32_SFLOAT, 3 * sizeof(float)}, // lifeRatio
|
||||
{2, 0, VK_FORMAT_R32_SFLOAT, 4 * sizeof(float)}, // size
|
||||
{3, 0, VK_FORMAT_R32_SFLOAT, 5 * sizeof(float)}, // isSpark
|
||||
};
|
||||
|
||||
smokePipeline_ = PipelineBuilder()
|
||||
.setShaders(smokeVert.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
smokeFrag.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||
.setVertexInput({sBind}, sAttrs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_POINT_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(smokePipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
||||
.build(device);
|
||||
}
|
||||
|
||||
m2Vert.destroy(); m2Frag.destroy();
|
||||
particleVert.destroy(); particleFrag.destroy();
|
||||
smokeVert.destroy(); smokeFrag.destroy();
|
||||
|
||||
core::Logger::getInstance().info("M2Renderer: pipelines recreated");
|
||||
}
|
||||
|
||||
} // namespace rendering
|
||||
} // namespace wowee
|
||||
|
|
|
|||
|
|
@ -446,7 +446,7 @@ void Minimap::render(VkCommandBuffer cmd, const Camera& playerCamera,
|
|||
float pixelW = static_cast<float>(mapSize) / screenWidth;
|
||||
float pixelH = static_cast<float>(mapSize) / screenHeight;
|
||||
float x = 1.0f - pixelW - margin / screenWidth;
|
||||
float y = 1.0f - pixelH - margin / screenHeight;
|
||||
float y = margin / screenHeight; // top edge in Vulkan (y=0 is top)
|
||||
|
||||
// Compute player's UV in the composite texture
|
||||
constexpr float TILE_SIZE = core::coords::TILE_SIZE;
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ bool MountDust::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout)
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -145,6 +146,65 @@ void MountDust::shutdown() {
|
|||
particles.clear();
|
||||
}
|
||||
|
||||
void MountDust::recreatePipelines() {
|
||||
if (!vkCtx) return;
|
||||
VkDevice device = vkCtx->getDevice();
|
||||
|
||||
// Destroy old pipeline (NOT layout)
|
||||
if (pipeline != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device, pipeline, nullptr);
|
||||
pipeline = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkShaderModule vertModule;
|
||||
vertModule.loadFromFile(device, "assets/shaders/mount_dust.vert.spv");
|
||||
VkShaderModule fragModule;
|
||||
fragModule.loadFromFile(device, "assets/shaders/mount_dust.frag.spv");
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
VkVertexInputBindingDescription binding{};
|
||||
binding.binding = 0;
|
||||
binding.stride = 5 * sizeof(float);
|
||||
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
std::vector<VkVertexInputAttributeDescription> attrs(3);
|
||||
attrs[0].location = 0;
|
||||
attrs[0].binding = 0;
|
||||
attrs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
|
||||
attrs[0].offset = 0;
|
||||
attrs[1].location = 1;
|
||||
attrs[1].binding = 0;
|
||||
attrs[1].format = VK_FORMAT_R32_SFLOAT;
|
||||
attrs[1].offset = 3 * sizeof(float);
|
||||
attrs[2].location = 2;
|
||||
attrs[2].binding = 0;
|
||||
attrs[2].format = VK_FORMAT_R32_SFLOAT;
|
||||
attrs[2].offset = 4 * sizeof(float);
|
||||
|
||||
std::vector<VkDynamicState> dynamicStates = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR
|
||||
};
|
||||
|
||||
pipeline = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, attrs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_POINT_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
}
|
||||
|
||||
void MountDust::spawnDust(const glm::vec3& position, const glm::vec3& velocity, bool isMoving) {
|
||||
if (!isMoving) {
|
||||
spawnAccum = 0.0f;
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ bool QuestMarkerRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFr
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS) // depth test on, write off
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(vkCtx_->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -179,6 +180,63 @@ void QuestMarkerRenderer::shutdown() {
|
|||
vkCtx_ = nullptr;
|
||||
}
|
||||
|
||||
void QuestMarkerRenderer::recreatePipelines() {
|
||||
if (!vkCtx_) return;
|
||||
VkDevice device = vkCtx_->getDevice();
|
||||
|
||||
// Destroy old pipeline (NOT layout)
|
||||
if (pipeline_ != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device, pipeline_, nullptr);
|
||||
pipeline_ = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkShaderModule vertModule;
|
||||
vertModule.loadFromFile(device, "assets/shaders/quest_marker.vert.spv");
|
||||
VkShaderModule fragModule;
|
||||
fragModule.loadFromFile(device, "assets/shaders/quest_marker.frag.spv");
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
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 uvAttr{};
|
||||
uvAttr.location = 1;
|
||||
uvAttr.binding = 0;
|
||||
uvAttr.format = VK_FORMAT_R32G32_SFLOAT;
|
||||
uvAttr.offset = 3 * sizeof(float);
|
||||
|
||||
std::vector<VkDynamicState> dynamicStates = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR
|
||||
};
|
||||
|
||||
pipeline_ = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, {posAttr, uvAttr})
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(vkCtx_->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
}
|
||||
|
||||
void QuestMarkerRenderer::createDescriptorResources() {
|
||||
VkDevice device = vkCtx_->getDevice();
|
||||
|
||||
|
|
|
|||
|
|
@ -723,6 +723,66 @@ void Renderer::shutdown() {
|
|||
LOG_INFO("Renderer shutdown");
|
||||
}
|
||||
|
||||
void Renderer::setMsaaSamples(VkSampleCountFlagBits samples) {
|
||||
if (!vkCtx) return;
|
||||
|
||||
VkSampleCountFlagBits current = vkCtx->getMsaaSamples();
|
||||
if (samples == current) return;
|
||||
|
||||
LOG_INFO("Changing MSAA from ", static_cast<int>(current), "x to ", static_cast<int>(samples), "x");
|
||||
|
||||
vkDeviceWaitIdle(vkCtx->getDevice());
|
||||
|
||||
// Set new MSAA and recreate swapchain (render pass, depth, MSAA image, framebuffers)
|
||||
vkCtx->setMsaaSamples(samples);
|
||||
vkCtx->recreateSwapchain(window->getWidth(), window->getHeight());
|
||||
|
||||
// Recreate all sub-renderer pipelines (they embed sample count from render pass)
|
||||
if (terrainRenderer) terrainRenderer->recreatePipelines();
|
||||
if (waterRenderer) waterRenderer->recreatePipelines();
|
||||
if (wmoRenderer) wmoRenderer->recreatePipelines();
|
||||
if (m2Renderer) m2Renderer->recreatePipelines();
|
||||
if (characterRenderer) characterRenderer->recreatePipelines();
|
||||
if (questMarkerRenderer) questMarkerRenderer->recreatePipelines();
|
||||
if (weather) weather->recreatePipelines();
|
||||
if (swimEffects) swimEffects->recreatePipelines();
|
||||
if (mountDust) mountDust->recreatePipelines();
|
||||
if (chargeEffect) chargeEffect->recreatePipelines();
|
||||
|
||||
// Sky system sub-renderers
|
||||
if (skySystem) {
|
||||
if (auto* sb = skySystem->getSkybox()) sb->recreatePipelines();
|
||||
if (auto* sf = skySystem->getStarField()) sf->recreatePipelines();
|
||||
if (auto* ce = skySystem->getCelestial()) ce->recreatePipelines();
|
||||
if (auto* cl = skySystem->getClouds()) cl->recreatePipelines();
|
||||
if (auto* lf = skySystem->getLensFlare()) lf->recreatePipelines();
|
||||
}
|
||||
|
||||
// Lightning is standalone (not instantiated in Renderer, no action needed)
|
||||
// Selection circle + overlay use lazy init, just destroy them
|
||||
VkDevice device = vkCtx->getDevice();
|
||||
if (selCirclePipeline) { vkDestroyPipeline(device, selCirclePipeline, nullptr); selCirclePipeline = VK_NULL_HANDLE; }
|
||||
if (overlayPipeline) { vkDestroyPipeline(device, overlayPipeline, nullptr); overlayPipeline = VK_NULL_HANDLE; }
|
||||
|
||||
// Reinitialize ImGui Vulkan backend with new MSAA sample count
|
||||
ImGui_ImplVulkan_Shutdown();
|
||||
ImGui_ImplVulkan_InitInfo initInfo{};
|
||||
initInfo.ApiVersion = VK_API_VERSION_1_1;
|
||||
initInfo.Instance = vkCtx->getInstance();
|
||||
initInfo.PhysicalDevice = vkCtx->getPhysicalDevice();
|
||||
initInfo.Device = vkCtx->getDevice();
|
||||
initInfo.QueueFamily = vkCtx->getGraphicsQueueFamily();
|
||||
initInfo.Queue = vkCtx->getGraphicsQueue();
|
||||
initInfo.DescriptorPool = vkCtx->getImGuiDescriptorPool();
|
||||
initInfo.MinImageCount = 2;
|
||||
initInfo.ImageCount = vkCtx->getSwapchainImageCount();
|
||||
initInfo.PipelineInfoMain.RenderPass = vkCtx->getImGuiRenderPass();
|
||||
initInfo.PipelineInfoMain.MSAASamples = vkCtx->getMsaaSamples();
|
||||
ImGui_ImplVulkan_Init(&initInfo);
|
||||
|
||||
LOG_INFO("MSAA change complete");
|
||||
}
|
||||
|
||||
void Renderer::beginFrame() {
|
||||
if (!vkCtx) return;
|
||||
|
||||
|
|
@ -2784,6 +2844,7 @@ void Renderer::initSelectionCircle() {
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setNoDepthTest()
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(selCirclePipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
||||
|
|
@ -2894,6 +2955,7 @@ void Renderer::initOverlayPipeline() {
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setNoDepthTest()
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(overlayPipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ bool Skybox::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) {
|
|||
.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)
|
||||
|
|
@ -84,6 +85,53 @@ bool Skybox::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) {
|
|||
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);
|
||||
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ bool StarField::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout)
|
|||
.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())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.build(device);
|
||||
|
|
@ -108,6 +109,71 @@ bool StarField::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout)
|
|||
return true;
|
||||
}
|
||||
|
||||
void StarField::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/starfield.vert.spv")) {
|
||||
LOG_ERROR("StarField::recreatePipelines: failed to load vertex shader");
|
||||
return;
|
||||
}
|
||||
VkShaderModule fragModule;
|
||||
if (!fragModule.loadFromFile(device, "assets/shaders/starfield.frag.spv")) {
|
||||
LOG_ERROR("StarField::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);
|
||||
|
||||
// Vertex input (same as initialize)
|
||||
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)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAdditive())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
|
||||
if (pipeline == VK_NULL_HANDLE) {
|
||||
LOG_ERROR("StarField::recreatePipelines: failed to create pipeline");
|
||||
}
|
||||
}
|
||||
|
||||
void StarField::shutdown() {
|
||||
destroyStarBuffers();
|
||||
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ bool SwimEffects::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(ripplePipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -136,6 +137,7 @@ bool SwimEffects::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(bubblePipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -225,6 +227,100 @@ void SwimEffects::shutdown() {
|
|||
bubbles.clear();
|
||||
}
|
||||
|
||||
void SwimEffects::recreatePipelines() {
|
||||
if (!vkCtx) return;
|
||||
VkDevice device = vkCtx->getDevice();
|
||||
|
||||
// Destroy old pipelines (NOT layouts)
|
||||
if (ripplePipeline != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device, ripplePipeline, nullptr);
|
||||
ripplePipeline = VK_NULL_HANDLE;
|
||||
}
|
||||
if (bubblePipeline != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device, bubblePipeline, nullptr);
|
||||
bubblePipeline = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
// Shared vertex input: pos(vec3) + size(float) + alpha(float) = 5 floats
|
||||
VkVertexInputBindingDescription binding{};
|
||||
binding.binding = 0;
|
||||
binding.stride = 5 * sizeof(float);
|
||||
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
std::vector<VkVertexInputAttributeDescription> attrs(3);
|
||||
attrs[0].location = 0;
|
||||
attrs[0].binding = 0;
|
||||
attrs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
|
||||
attrs[0].offset = 0;
|
||||
attrs[1].location = 1;
|
||||
attrs[1].binding = 0;
|
||||
attrs[1].format = VK_FORMAT_R32_SFLOAT;
|
||||
attrs[1].offset = 3 * sizeof(float);
|
||||
attrs[2].location = 2;
|
||||
attrs[2].binding = 0;
|
||||
attrs[2].format = VK_FORMAT_R32_SFLOAT;
|
||||
attrs[2].offset = 4 * sizeof(float);
|
||||
|
||||
std::vector<VkDynamicState> dynamicStates = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR
|
||||
};
|
||||
|
||||
// ---- Rebuild ripple pipeline ----
|
||||
{
|
||||
VkShaderModule vertModule;
|
||||
vertModule.loadFromFile(device, "assets/shaders/swim_ripple.vert.spv");
|
||||
VkShaderModule fragModule;
|
||||
fragModule.loadFromFile(device, "assets/shaders/swim_ripple.frag.spv");
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
ripplePipeline = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, attrs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_POINT_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(ripplePipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
}
|
||||
|
||||
// ---- Rebuild bubble pipeline ----
|
||||
{
|
||||
VkShaderModule vertModule;
|
||||
vertModule.loadFromFile(device, "assets/shaders/swim_bubble.vert.spv");
|
||||
VkShaderModule fragModule;
|
||||
fragModule.loadFromFile(device, "assets/shaders/swim_bubble.frag.spv");
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
bubblePipeline = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, attrs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_POINT_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(bubblePipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void SwimEffects::spawnRipple(const glm::vec3& pos, const glm::vec3& moveDir, float waterH) {
|
||||
if (static_cast<int>(ripples.size()) >= MAX_RIPPLE_PARTICLES) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ bool TerrainRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameL
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, true, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendDisabled())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
|
|
@ -159,6 +160,7 @@ bool TerrainRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameL
|
|||
.setRasterization(VK_POLYGON_MODE_LINE, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, true, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendDisabled())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
|
|
@ -188,6 +190,86 @@ bool TerrainRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameL
|
|||
return true;
|
||||
}
|
||||
|
||||
void TerrainRenderer::recreatePipelines() {
|
||||
if (!vkCtx) return;
|
||||
VkDevice device = vkCtx->getDevice();
|
||||
|
||||
// Destroy old pipelines (keep layouts)
|
||||
if (pipeline) { vkDestroyPipeline(device, pipeline, nullptr); pipeline = VK_NULL_HANDLE; }
|
||||
if (wireframePipeline) { vkDestroyPipeline(device, wireframePipeline, nullptr); wireframePipeline = VK_NULL_HANDLE; }
|
||||
|
||||
// Load shaders
|
||||
VkShaderModule vertShader, fragShader;
|
||||
if (!vertShader.loadFromFile(device, "assets/shaders/terrain.vert.spv")) {
|
||||
LOG_ERROR("TerrainRenderer::recreatePipelines: failed to load vertex shader");
|
||||
return;
|
||||
}
|
||||
if (!fragShader.loadFromFile(device, "assets/shaders/terrain.frag.spv")) {
|
||||
LOG_ERROR("TerrainRenderer::recreatePipelines: failed to load fragment shader");
|
||||
vertShader.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// Vertex input (same as initialize)
|
||||
VkVertexInputBindingDescription vertexBinding{};
|
||||
vertexBinding.binding = 0;
|
||||
vertexBinding.stride = sizeof(pipeline::TerrainVertex);
|
||||
vertexBinding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
std::vector<VkVertexInputAttributeDescription> vertexAttribs(4);
|
||||
vertexAttribs[0] = { 0, 0, VK_FORMAT_R32G32B32_SFLOAT,
|
||||
static_cast<uint32_t>(offsetof(pipeline::TerrainVertex, position)) };
|
||||
vertexAttribs[1] = { 1, 0, VK_FORMAT_R32G32B32_SFLOAT,
|
||||
static_cast<uint32_t>(offsetof(pipeline::TerrainVertex, normal)) };
|
||||
vertexAttribs[2] = { 2, 0, VK_FORMAT_R32G32_SFLOAT,
|
||||
static_cast<uint32_t>(offsetof(pipeline::TerrainVertex, texCoord)) };
|
||||
vertexAttribs[3] = { 3, 0, VK_FORMAT_R32G32_SFLOAT,
|
||||
static_cast<uint32_t>(offsetof(pipeline::TerrainVertex, layerUV)) };
|
||||
|
||||
VkRenderPass mainPass = vkCtx->getImGuiRenderPass();
|
||||
|
||||
// Rebuild fill pipeline
|
||||
pipeline = PipelineBuilder()
|
||||
.setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
fragShader.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||
.setVertexInput({ vertexBinding }, vertexAttribs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, true, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendDisabled())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
.build(device);
|
||||
|
||||
if (!pipeline) {
|
||||
LOG_ERROR("TerrainRenderer::recreatePipelines: failed to create fill pipeline");
|
||||
}
|
||||
|
||||
// Rebuild wireframe pipeline
|
||||
wireframePipeline = PipelineBuilder()
|
||||
.setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
fragShader.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||
.setVertexInput({ vertexBinding }, vertexAttribs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_LINE, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, true, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendDisabled())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
.build(device);
|
||||
|
||||
if (!wireframePipeline) {
|
||||
LOG_WARNING("TerrainRenderer::recreatePipelines: wireframe pipeline not available");
|
||||
}
|
||||
|
||||
vertShader.destroy();
|
||||
fragShader.destroy();
|
||||
}
|
||||
|
||||
void TerrainRenderer::shutdown() {
|
||||
LOG_INFO("Shutting down terrain renderer");
|
||||
|
||||
|
|
@ -482,7 +564,39 @@ void TerrainRenderer::writeMaterialDescriptors(VkDescriptorSet set, const Terrai
|
|||
}
|
||||
|
||||
void TerrainRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const Camera& camera) {
|
||||
if (chunks.empty() || !pipeline) return;
|
||||
if (chunks.empty() || !pipeline) {
|
||||
static int emptyLog = 0;
|
||||
if (++emptyLog <= 3)
|
||||
LOG_WARNING("TerrainRenderer::render: chunks=", chunks.size(), " pipeline=", (pipeline != VK_NULL_HANDLE));
|
||||
return;
|
||||
}
|
||||
|
||||
// One-time diagnostic: log chunk nearest to camera
|
||||
static bool loggedDiag = false;
|
||||
if (!loggedDiag && !chunks.empty()) {
|
||||
loggedDiag = true;
|
||||
glm::vec3 cam = camera.getPosition();
|
||||
// Find chunk nearest to camera
|
||||
const TerrainChunkGPU* nearest = nullptr;
|
||||
float nearestDist = 1e30f;
|
||||
for (const auto& ch : chunks) {
|
||||
float dx = ch.boundingSphereCenter.x - cam.x;
|
||||
float dy = ch.boundingSphereCenter.y - cam.y;
|
||||
float dz = ch.boundingSphereCenter.z - cam.z;
|
||||
float d = dx*dx + dy*dy + dz*dz;
|
||||
if (d < nearestDist) { nearestDist = d; nearest = &ch; }
|
||||
}
|
||||
if (nearest) {
|
||||
float d2d = std::sqrt((nearest->boundingSphereCenter.x-cam.x)*(nearest->boundingSphereCenter.x-cam.x) +
|
||||
(nearest->boundingSphereCenter.y-cam.y)*(nearest->boundingSphereCenter.y-cam.y));
|
||||
LOG_INFO("Terrain diag: chunks=", chunks.size(),
|
||||
" cam=(", cam.x, ",", cam.y, ",", cam.z, ")",
|
||||
" nearest_center=(", nearest->boundingSphereCenter.x, ",", nearest->boundingSphereCenter.y, ",", nearest->boundingSphereCenter.z, ")",
|
||||
" dist2d=", d2d, " dist3d=", std::sqrt(nearestDist),
|
||||
" radius=", nearest->boundingSphereRadius,
|
||||
" matSet=", (nearest->materialSet != VK_NULL_HANDLE ? "ok" : "NULL"));
|
||||
}
|
||||
}
|
||||
|
||||
VkPipeline activePipeline = (wireframe && wireframePipeline) ? wireframePipeline : pipeline;
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, activePipeline);
|
||||
|
|
@ -507,6 +621,13 @@ void TerrainRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, c
|
|||
renderedChunks = 0;
|
||||
culledChunks = 0;
|
||||
|
||||
// Periodic culling summary (every ~5s at 60fps)
|
||||
static int renderCallCount = 0;
|
||||
if (++renderCallCount % 300 == 1) {
|
||||
glm::vec3 cam = camera.getPosition();
|
||||
LOG_INFO("Terrain render call: total=", chunks.size(), " cam=(", cam.x, ",", cam.y, ",", cam.z, ")");
|
||||
}
|
||||
|
||||
for (const auto& chunk : chunks) {
|
||||
if (!chunk.isValid() || !chunk.materialSet) continue;
|
||||
|
||||
|
|
@ -533,6 +654,11 @@ void TerrainRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, c
|
|||
vkCmdDrawIndexed(cmd, chunk.indexCount, 1, 0, 0, 0);
|
||||
renderedChunks++;
|
||||
}
|
||||
|
||||
// Log culling result periodically
|
||||
if (renderCallCount % 300 == 1) {
|
||||
LOG_INFO("Terrain culling: rendered=", renderedChunks, " culled=", culledChunks);
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainRenderer::renderShadow(VkCommandBuffer /*cmd*/, const glm::vec3& /*shadowCenter*/, float /*halfExtent*/) {
|
||||
|
|
@ -589,6 +715,13 @@ void TerrainRenderer::destroyChunkGPU(TerrainChunkGPU& chunk) {
|
|||
chunk.paramsUBO = VK_NULL_HANDLE;
|
||||
}
|
||||
chunk.materialSet = VK_NULL_HANDLE;
|
||||
|
||||
// Destroy owned alpha textures (VkTexture::~VkTexture is a no-op, must call destroy() explicitly)
|
||||
VkDevice device = vkCtx->getDevice();
|
||||
for (auto& tex : chunk.ownedAlphaTextures) {
|
||||
if (tex) tex->destroy(device, allocator);
|
||||
}
|
||||
chunk.ownedAlphaTextures.clear();
|
||||
}
|
||||
|
||||
int TerrainRenderer::getTriangleCount() const {
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ bool VkContext::createSwapchain(int width, int height) {
|
|||
vkb::SwapchainBuilder swapchainBuilder{physicalDevice, device, surface};
|
||||
|
||||
auto swapRet = swapchainBuilder
|
||||
.set_desired_format({VK_FORMAT_B8G8R8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR})
|
||||
.set_desired_format({VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR})
|
||||
.set_desired_present_mode(VK_PRESENT_MODE_FIFO_KHR) // VSync
|
||||
.set_desired_extent(static_cast<uint32_t>(width), static_cast<uint32_t>(height))
|
||||
.set_desired_min_image_count(2)
|
||||
|
|
@ -331,7 +331,7 @@ bool VkContext::createDepthBuffer() {
|
|||
imgInfo.extent = {swapchainExtent.width, swapchainExtent.height, 1};
|
||||
imgInfo.mipLevels = 1;
|
||||
imgInfo.arrayLayers = 1;
|
||||
imgInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
imgInfo.samples = msaaSamples_;
|
||||
imgInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
imgInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||
|
||||
|
|
@ -365,11 +365,175 @@ void VkContext::destroyDepthBuffer() {
|
|||
if (depthImage) { vmaDestroyImage(allocator, depthImage, depthAllocation); depthImage = VK_NULL_HANDLE; depthAllocation = VK_NULL_HANDLE; }
|
||||
}
|
||||
|
||||
bool VkContext::createMsaaColorImage() {
|
||||
if (msaaSamples_ == VK_SAMPLE_COUNT_1_BIT) return true; // No MSAA image needed
|
||||
|
||||
VkImageCreateInfo imgInfo{};
|
||||
imgInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
imgInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
imgInfo.format = swapchainFormat;
|
||||
imgInfo.extent = {swapchainExtent.width, swapchainExtent.height, 1};
|
||||
imgInfo.mipLevels = 1;
|
||||
imgInfo.arrayLayers = 1;
|
||||
imgInfo.samples = msaaSamples_;
|
||||
imgInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
imgInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
|
||||
|
||||
VmaAllocationCreateInfo allocInfo{};
|
||||
allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
|
||||
allocInfo.preferredFlags = VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT;
|
||||
|
||||
if (vmaCreateImage(allocator, &imgInfo, &allocInfo, &msaaColorImage_, &msaaColorAllocation_, nullptr) != VK_SUCCESS) {
|
||||
LOG_ERROR("Failed to create MSAA color image");
|
||||
return false;
|
||||
}
|
||||
|
||||
VkImageViewCreateInfo viewInfo{};
|
||||
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
viewInfo.image = msaaColorImage_;
|
||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.format = swapchainFormat;
|
||||
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
viewInfo.subresourceRange.levelCount = 1;
|
||||
viewInfo.subresourceRange.layerCount = 1;
|
||||
|
||||
if (vkCreateImageView(device, &viewInfo, nullptr, &msaaColorView_) != VK_SUCCESS) {
|
||||
LOG_ERROR("Failed to create MSAA color image view");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VkContext::destroyMsaaColorImage() {
|
||||
if (msaaColorView_) { vkDestroyImageView(device, msaaColorView_, nullptr); msaaColorView_ = VK_NULL_HANDLE; }
|
||||
if (msaaColorImage_) { vmaDestroyImage(allocator, msaaColorImage_, msaaColorAllocation_); msaaColorImage_ = VK_NULL_HANDLE; msaaColorAllocation_ = VK_NULL_HANDLE; }
|
||||
}
|
||||
|
||||
VkSampleCountFlagBits VkContext::getMaxUsableSampleCount() const {
|
||||
VkPhysicalDeviceProperties props;
|
||||
vkGetPhysicalDeviceProperties(physicalDevice, &props);
|
||||
VkSampleCountFlags counts = props.limits.framebufferColorSampleCounts
|
||||
& props.limits.framebufferDepthSampleCounts;
|
||||
if (counts & VK_SAMPLE_COUNT_8_BIT) return VK_SAMPLE_COUNT_8_BIT;
|
||||
if (counts & VK_SAMPLE_COUNT_4_BIT) return VK_SAMPLE_COUNT_4_BIT;
|
||||
if (counts & VK_SAMPLE_COUNT_2_BIT) return VK_SAMPLE_COUNT_2_BIT;
|
||||
return VK_SAMPLE_COUNT_1_BIT;
|
||||
}
|
||||
|
||||
void VkContext::setMsaaSamples(VkSampleCountFlagBits samples) {
|
||||
// Clamp to max supported
|
||||
VkSampleCountFlagBits maxSamples = getMaxUsableSampleCount();
|
||||
if (samples > maxSamples) samples = maxSamples;
|
||||
msaaSamples_ = samples;
|
||||
swapchainDirty = true;
|
||||
}
|
||||
|
||||
bool VkContext::createImGuiResources() {
|
||||
// Create depth buffer first
|
||||
if (!createDepthBuffer()) return false;
|
||||
|
||||
// Render pass with color + depth attachments (used by both scene and ImGui)
|
||||
// Create MSAA color image if needed
|
||||
if (!createMsaaColorImage()) return false;
|
||||
|
||||
bool useMsaa = (msaaSamples_ > VK_SAMPLE_COUNT_1_BIT);
|
||||
|
||||
if (useMsaa) {
|
||||
// MSAA render pass: 3 attachments (MSAA color, depth, resolve/swapchain)
|
||||
VkAttachmentDescription attachments[3] = {};
|
||||
|
||||
// Attachment 0: MSAA color target
|
||||
attachments[0].format = swapchainFormat;
|
||||
attachments[0].samples = msaaSamples_;
|
||||
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
|
||||
// Attachment 1: Depth (multisampled)
|
||||
attachments[1].format = depthFormat;
|
||||
attachments[1].samples = msaaSamples_;
|
||||
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
|
||||
// Attachment 2: Resolve target (swapchain image)
|
||||
attachments[2].format = swapchainFormat;
|
||||
attachments[2].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
attachments[2].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[2].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachments[2].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[2].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[2].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[2].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
|
||||
VkAttachmentReference colorRef{};
|
||||
colorRef.attachment = 0;
|
||||
colorRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
|
||||
VkAttachmentReference depthRef{};
|
||||
depthRef.attachment = 1;
|
||||
depthRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
|
||||
VkAttachmentReference resolveRef{};
|
||||
resolveRef.attachment = 2;
|
||||
resolveRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
|
||||
VkSubpassDescription subpass{};
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.colorAttachmentCount = 1;
|
||||
subpass.pColorAttachments = &colorRef;
|
||||
subpass.pDepthStencilAttachment = &depthRef;
|
||||
subpass.pResolveAttachments = &resolveRef;
|
||||
|
||||
VkSubpassDependency dependency{};
|
||||
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependency.dstSubpass = 0;
|
||||
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.srcAccessMask = 0;
|
||||
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
|
||||
VkRenderPassCreateInfo rpInfo{};
|
||||
rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
||||
rpInfo.attachmentCount = 3;
|
||||
rpInfo.pAttachments = attachments;
|
||||
rpInfo.subpassCount = 1;
|
||||
rpInfo.pSubpasses = &subpass;
|
||||
rpInfo.dependencyCount = 1;
|
||||
rpInfo.pDependencies = &dependency;
|
||||
|
||||
if (vkCreateRenderPass(device, &rpInfo, nullptr, &imguiRenderPass) != VK_SUCCESS) {
|
||||
LOG_ERROR("Failed to create MSAA render pass");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Framebuffers: [msaaColorView, depthView, swapchainView]
|
||||
swapchainFramebuffers.resize(swapchainImageViews.size());
|
||||
for (size_t i = 0; i < swapchainImageViews.size(); i++) {
|
||||
VkImageView fbAttachments[3] = {msaaColorView_, depthImageView, swapchainImageViews[i]};
|
||||
|
||||
VkFramebufferCreateInfo fbInfo{};
|
||||
fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||
fbInfo.renderPass = imguiRenderPass;
|
||||
fbInfo.attachmentCount = 3;
|
||||
fbInfo.pAttachments = fbAttachments;
|
||||
fbInfo.width = swapchainExtent.width;
|
||||
fbInfo.height = swapchainExtent.height;
|
||||
fbInfo.layers = 1;
|
||||
|
||||
if (vkCreateFramebuffer(device, &fbInfo, nullptr, &swapchainFramebuffers[i]) != VK_SUCCESS) {
|
||||
LOG_ERROR("Failed to create MSAA swapchain framebuffer ", i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Non-MSAA render pass: 2 attachments (color + depth) — original path
|
||||
VkAttachmentDescription attachments[2] = {};
|
||||
|
||||
// Color attachment (swapchain image)
|
||||
|
|
@ -428,7 +592,7 @@ bool VkContext::createImGuiResources() {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Create framebuffers (color + depth)
|
||||
// Framebuffers: [swapchainView, depthView]
|
||||
swapchainFramebuffers.resize(swapchainImageViews.size());
|
||||
for (size_t i = 0; i < swapchainImageViews.size(); i++) {
|
||||
VkImageView fbAttachments[2] = {swapchainImageViews[i], depthImageView};
|
||||
|
|
@ -447,6 +611,7 @@ bool VkContext::createImGuiResources() {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create descriptor pool for ImGui
|
||||
VkDescriptorPoolSize poolSizes[] = {
|
||||
|
|
@ -473,6 +638,7 @@ void VkContext::destroyImGuiResources() {
|
|||
vkDestroyDescriptorPool(device, imguiDescriptorPool, nullptr);
|
||||
imguiDescriptorPool = VK_NULL_HANDLE;
|
||||
}
|
||||
destroyMsaaColorImage();
|
||||
destroyDepthBuffer();
|
||||
// Framebuffers are destroyed in destroySwapchain()
|
||||
if (imguiRenderPass) {
|
||||
|
|
@ -500,7 +666,7 @@ bool VkContext::recreateSwapchain(int width, int height) {
|
|||
|
||||
vkb::SwapchainBuilder swapchainBuilder{physicalDevice, device, surface};
|
||||
auto swapRet = swapchainBuilder
|
||||
.set_desired_format({VK_FORMAT_B8G8R8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR})
|
||||
.set_desired_format({VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR})
|
||||
.set_desired_present_mode(VK_PRESENT_MODE_FIFO_KHR)
|
||||
.set_desired_extent(static_cast<uint32_t>(width), static_cast<uint32_t>(height))
|
||||
.set_desired_min_image_count(2)
|
||||
|
|
@ -524,15 +690,155 @@ bool VkContext::recreateSwapchain(int width, int height) {
|
|||
swapchainImages = vkbSwap.get_images().value();
|
||||
swapchainImageViews = vkbSwap.get_image_views().value();
|
||||
|
||||
// Recreate depth buffer
|
||||
// Recreate depth buffer + MSAA color image
|
||||
destroyMsaaColorImage();
|
||||
destroyDepthBuffer();
|
||||
if (!createDepthBuffer()) return false;
|
||||
|
||||
// Recreate framebuffers (color + depth)
|
||||
// Destroy old render pass (needs recreation if MSAA changed)
|
||||
if (imguiRenderPass) {
|
||||
vkDestroyRenderPass(device, imguiRenderPass, nullptr);
|
||||
imguiRenderPass = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (!createDepthBuffer()) return false;
|
||||
if (!createMsaaColorImage()) return false;
|
||||
|
||||
bool useMsaa = (msaaSamples_ > VK_SAMPLE_COUNT_1_BIT);
|
||||
|
||||
if (useMsaa) {
|
||||
// MSAA render pass: 3 attachments
|
||||
VkAttachmentDescription attachments[3] = {};
|
||||
attachments[0].format = swapchainFormat;
|
||||
attachments[0].samples = msaaSamples_;
|
||||
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
|
||||
attachments[1].format = depthFormat;
|
||||
attachments[1].samples = msaaSamples_;
|
||||
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
|
||||
attachments[2].format = swapchainFormat;
|
||||
attachments[2].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
attachments[2].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[2].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachments[2].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[2].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[2].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[2].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
|
||||
VkAttachmentReference colorRef{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
||||
VkAttachmentReference depthRef{1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL};
|
||||
VkAttachmentReference resolveRef{2, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
||||
|
||||
VkSubpassDescription subpass{};
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.colorAttachmentCount = 1;
|
||||
subpass.pColorAttachments = &colorRef;
|
||||
subpass.pDepthStencilAttachment = &depthRef;
|
||||
subpass.pResolveAttachments = &resolveRef;
|
||||
|
||||
VkSubpassDependency dependency{};
|
||||
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependency.dstSubpass = 0;
|
||||
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.srcAccessMask = 0;
|
||||
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
|
||||
VkRenderPassCreateInfo rpInfo{};
|
||||
rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
||||
rpInfo.attachmentCount = 3;
|
||||
rpInfo.pAttachments = attachments;
|
||||
rpInfo.subpassCount = 1;
|
||||
rpInfo.pSubpasses = &subpass;
|
||||
rpInfo.dependencyCount = 1;
|
||||
rpInfo.pDependencies = &dependency;
|
||||
|
||||
if (vkCreateRenderPass(device, &rpInfo, nullptr, &imguiRenderPass) != VK_SUCCESS) {
|
||||
LOG_ERROR("Failed to recreate MSAA render pass");
|
||||
return false;
|
||||
}
|
||||
|
||||
swapchainFramebuffers.resize(swapchainImageViews.size());
|
||||
for (size_t i = 0; i < swapchainImageViews.size(); i++) {
|
||||
VkImageView fbAttachments[3] = {msaaColorView_, depthImageView, swapchainImageViews[i]};
|
||||
VkFramebufferCreateInfo fbInfo{};
|
||||
fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||
fbInfo.renderPass = imguiRenderPass;
|
||||
fbInfo.attachmentCount = 3;
|
||||
fbInfo.pAttachments = fbAttachments;
|
||||
fbInfo.width = swapchainExtent.width;
|
||||
fbInfo.height = swapchainExtent.height;
|
||||
fbInfo.layers = 1;
|
||||
if (vkCreateFramebuffer(device, &fbInfo, nullptr, &swapchainFramebuffers[i]) != VK_SUCCESS) {
|
||||
LOG_ERROR("Failed to recreate MSAA swapchain framebuffer ", i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Non-MSAA render pass: 2 attachments
|
||||
VkAttachmentDescription attachments[2] = {};
|
||||
attachments[0].format = swapchainFormat;
|
||||
attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
|
||||
attachments[1].format = depthFormat;
|
||||
attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
|
||||
VkAttachmentReference colorRef{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
||||
VkAttachmentReference depthRef{1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL};
|
||||
|
||||
VkSubpassDescription subpass{};
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.colorAttachmentCount = 1;
|
||||
subpass.pColorAttachments = &colorRef;
|
||||
subpass.pDepthStencilAttachment = &depthRef;
|
||||
|
||||
VkSubpassDependency dependency{};
|
||||
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependency.dstSubpass = 0;
|
||||
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.srcAccessMask = 0;
|
||||
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
|
||||
VkRenderPassCreateInfo rpInfo{};
|
||||
rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
||||
rpInfo.attachmentCount = 2;
|
||||
rpInfo.pAttachments = attachments;
|
||||
rpInfo.subpassCount = 1;
|
||||
rpInfo.pSubpasses = &subpass;
|
||||
rpInfo.dependencyCount = 1;
|
||||
rpInfo.pDependencies = &dependency;
|
||||
|
||||
if (vkCreateRenderPass(device, &rpInfo, nullptr, &imguiRenderPass) != VK_SUCCESS) {
|
||||
LOG_ERROR("Failed to recreate render pass");
|
||||
return false;
|
||||
}
|
||||
|
||||
swapchainFramebuffers.resize(swapchainImageViews.size());
|
||||
for (size_t i = 0; i < swapchainImageViews.size(); i++) {
|
||||
VkImageView fbAttachments[2] = {swapchainImageViews[i], depthImageView};
|
||||
|
||||
VkFramebufferCreateInfo fbInfo{};
|
||||
fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||
fbInfo.renderPass = imguiRenderPass;
|
||||
|
|
@ -541,12 +847,12 @@ bool VkContext::recreateSwapchain(int width, int height) {
|
|||
fbInfo.width = swapchainExtent.width;
|
||||
fbInfo.height = swapchainExtent.height;
|
||||
fbInfo.layers = 1;
|
||||
|
||||
if (vkCreateFramebuffer(device, &fbInfo, nullptr, &swapchainFramebuffers[i]) != VK_SUCCESS) {
|
||||
LOG_ERROR("Failed to recreate swapchain framebuffer ", i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
swapchainDirty = false;
|
||||
LOG_INFO("Swapchain recreated: ", swapchainExtent.width, "x", swapchainExtent.height);
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ bool WaterRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLay
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL) // depth test yes, write no
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
|
|
@ -142,6 +143,60 @@ bool WaterRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLay
|
|||
return true;
|
||||
}
|
||||
|
||||
void WaterRenderer::recreatePipelines() {
|
||||
if (!vkCtx) return;
|
||||
VkDevice device = vkCtx->getDevice();
|
||||
|
||||
// Destroy old pipeline (keep layout)
|
||||
if (waterPipeline) { vkDestroyPipeline(device, waterPipeline, nullptr); waterPipeline = VK_NULL_HANDLE; }
|
||||
|
||||
// Load shaders
|
||||
VkShaderModule vertShader, fragShader;
|
||||
if (!vertShader.loadFromFile(device, "assets/shaders/water.vert.spv")) {
|
||||
LOG_ERROR("WaterRenderer::recreatePipelines: failed to load vertex shader");
|
||||
return;
|
||||
}
|
||||
if (!fragShader.loadFromFile(device, "assets/shaders/water.frag.spv")) {
|
||||
LOG_ERROR("WaterRenderer::recreatePipelines: failed to load fragment shader");
|
||||
vertShader.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// Vertex input (same as initialize)
|
||||
VkVertexInputBindingDescription vertBinding{};
|
||||
vertBinding.binding = 0;
|
||||
vertBinding.stride = 8 * sizeof(float);
|
||||
vertBinding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
std::vector<VkVertexInputAttributeDescription> vertAttribs = {
|
||||
{ 0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0 },
|
||||
{ 1, 0, VK_FORMAT_R32G32_SFLOAT, 6 * sizeof(float) },
|
||||
};
|
||||
|
||||
VkRenderPass mainPass = vkCtx->getImGuiRenderPass();
|
||||
|
||||
waterPipeline = PipelineBuilder()
|
||||
.setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
fragShader.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||
.setVertexInput({ vertBinding }, vertAttribs)
|
||||
.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::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
.build(device);
|
||||
|
||||
vertShader.destroy();
|
||||
fragShader.destroy();
|
||||
|
||||
if (!waterPipeline) {
|
||||
LOG_ERROR("WaterRenderer::recreatePipelines: failed to create pipeline");
|
||||
}
|
||||
}
|
||||
|
||||
void WaterRenderer::shutdown() {
|
||||
clear();
|
||||
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ bool Weather::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) {
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS) // depth test on, write off (transparent particles)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
|
|
@ -115,6 +116,65 @@ bool Weather::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void Weather::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/weather.vert.spv")) {
|
||||
LOG_ERROR("Weather::recreatePipelines: failed to load vertex shader");
|
||||
return;
|
||||
}
|
||||
VkShaderModule fragModule;
|
||||
if (!fragModule.loadFromFile(device, "assets/shaders/weather.frag.spv")) {
|
||||
LOG_ERROR("Weather::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);
|
||||
|
||||
// Vertex input (same as initialize)
|
||||
VkVertexInputBindingDescription binding{};
|
||||
binding.binding = 0;
|
||||
binding.stride = 3 * 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;
|
||||
|
||||
std::vector<VkDynamicState> dynamicStates = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR
|
||||
};
|
||||
|
||||
pipeline = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, {posAttr})
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_POINT_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(pipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
|
||||
if (pipeline == VK_NULL_HANDLE) {
|
||||
LOG_ERROR("Weather::recreatePipelines: failed to create pipeline");
|
||||
}
|
||||
}
|
||||
|
||||
void Weather::update(const Camera& camera, float deltaTime) {
|
||||
if (!enabled || weatherType == Type::NONE) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ bool WMORenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, true, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendDisabled())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
|
|
@ -175,6 +176,7 @@ bool WMORenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou
|
|||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
|
|
@ -193,6 +195,7 @@ bool WMORenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou
|
|||
.setRasterization(VK_POLYGON_MODE_LINE, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, true, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendDisabled())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
|
|
@ -2878,5 +2881,96 @@ float WMORenderer::raycastBoundingBoxes(const glm::vec3& origin, const glm::vec3
|
|||
|
||||
// Occlusion queries stubbed out in Vulkan (were disabled by default anyway)
|
||||
|
||||
void WMORenderer::recreatePipelines() {
|
||||
if (!vkCtx_) return;
|
||||
VkDevice device = vkCtx_->getDevice();
|
||||
vkDeviceWaitIdle(device);
|
||||
|
||||
// Destroy old main-pass pipelines (NOT shadow, NOT pipeline layout)
|
||||
if (opaquePipeline_) { vkDestroyPipeline(device, opaquePipeline_, nullptr); opaquePipeline_ = VK_NULL_HANDLE; }
|
||||
if (transparentPipeline_) { vkDestroyPipeline(device, transparentPipeline_, nullptr); transparentPipeline_ = VK_NULL_HANDLE; }
|
||||
if (wireframePipeline_) { vkDestroyPipeline(device, wireframePipeline_, nullptr); wireframePipeline_ = VK_NULL_HANDLE; }
|
||||
|
||||
// --- Load shaders ---
|
||||
VkShaderModule vertShader, fragShader;
|
||||
if (!vertShader.loadFromFile(device, "assets/shaders/wmo.vert.spv") ||
|
||||
!fragShader.loadFromFile(device, "assets/shaders/wmo.frag.spv")) {
|
||||
core::Logger::getInstance().error("WMORenderer::recreatePipelines: failed to load shaders");
|
||||
return;
|
||||
}
|
||||
|
||||
// --- Vertex input ---
|
||||
struct WMOVertexData {
|
||||
glm::vec3 position;
|
||||
glm::vec3 normal;
|
||||
glm::vec2 texCoord;
|
||||
glm::vec4 color;
|
||||
};
|
||||
|
||||
VkVertexInputBindingDescription vertexBinding{};
|
||||
vertexBinding.binding = 0;
|
||||
vertexBinding.stride = sizeof(WMOVertexData);
|
||||
vertexBinding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
std::vector<VkVertexInputAttributeDescription> vertexAttribs(4);
|
||||
vertexAttribs[0] = { 0, 0, VK_FORMAT_R32G32B32_SFLOAT,
|
||||
static_cast<uint32_t>(offsetof(WMOVertexData, position)) };
|
||||
vertexAttribs[1] = { 1, 0, VK_FORMAT_R32G32B32_SFLOAT,
|
||||
static_cast<uint32_t>(offsetof(WMOVertexData, normal)) };
|
||||
vertexAttribs[2] = { 2, 0, VK_FORMAT_R32G32_SFLOAT,
|
||||
static_cast<uint32_t>(offsetof(WMOVertexData, texCoord)) };
|
||||
vertexAttribs[3] = { 3, 0, VK_FORMAT_R32G32B32A32_SFLOAT,
|
||||
static_cast<uint32_t>(offsetof(WMOVertexData, color)) };
|
||||
|
||||
VkRenderPass mainPass = vkCtx_->getImGuiRenderPass();
|
||||
|
||||
opaquePipeline_ = PipelineBuilder()
|
||||
.setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
fragShader.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||
.setVertexInput({ vertexBinding }, vertexAttribs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, true, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendDisabled())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
.build(device);
|
||||
|
||||
transparentPipeline_ = PipelineBuilder()
|
||||
.setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
fragShader.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||
.setVertexInput({ vertexBinding }, vertexAttribs)
|
||||
.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::blendAlpha())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
.build(device);
|
||||
|
||||
wireframePipeline_ = PipelineBuilder()
|
||||
.setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||
fragShader.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||
.setVertexInput({ vertexBinding }, vertexAttribs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_LINE, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, true, VK_COMPARE_OP_LESS_OR_EQUAL)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendDisabled())
|
||||
.setMultisample(vkCtx_->getMsaaSamples())
|
||||
.setLayout(pipelineLayout_)
|
||||
.setRenderPass(mainPass)
|
||||
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
|
||||
.build(device);
|
||||
|
||||
vertShader.destroy();
|
||||
fragShader.destroy();
|
||||
|
||||
core::Logger::getInstance().info("WMORenderer: pipelines recreated");
|
||||
}
|
||||
|
||||
} // namespace rendering
|
||||
} // namespace wowee
|
||||
|
|
|
|||
|
|
@ -3,10 +3,13 @@
|
|||
#include "core/application.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include "rendering/renderer.hpp"
|
||||
#include "rendering/vk_context.hpp"
|
||||
#include "pipeline/asset_manager.hpp"
|
||||
#include "audio/music_manager.hpp"
|
||||
#include "game/expansion_profile.hpp"
|
||||
#include <imgui.h>
|
||||
#include <imgui_impl_vulkan.h>
|
||||
#include "stb_image.h"
|
||||
#include <filesystem>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
|
|
@ -159,39 +162,34 @@ void AuthScreen::render(auth::AuthHandler& authHandler) {
|
|||
loginInfoLoaded = true;
|
||||
}
|
||||
|
||||
if (!videoInitAttempted) {
|
||||
videoInitAttempted = true;
|
||||
std::string videoPath = "assets/startscreen.mp4";
|
||||
if (!std::filesystem::exists(videoPath)) {
|
||||
videoPath = (std::filesystem::current_path() / "assets/startscreen.mp4").string();
|
||||
if (!bgInitAttempted) {
|
||||
bgInitAttempted = true;
|
||||
loadBackgroundImage();
|
||||
}
|
||||
backgroundVideo.open(videoPath);
|
||||
}
|
||||
backgroundVideo.update(ImGui::GetIO().DeltaTime);
|
||||
if (backgroundVideo.isReady()) {
|
||||
if (bgDescriptorSet) {
|
||||
ImVec2 screen = ImGui::GetIO().DisplaySize;
|
||||
float screenW = screen.x;
|
||||
float screenH = screen.y;
|
||||
float videoW = static_cast<float>(backgroundVideo.getWidth());
|
||||
float videoH = static_cast<float>(backgroundVideo.getHeight());
|
||||
if (videoW > 0.0f && videoH > 0.0f) {
|
||||
float imgW = static_cast<float>(bgWidth);
|
||||
float imgH = static_cast<float>(bgHeight);
|
||||
if (imgW > 0.0f && imgH > 0.0f) {
|
||||
float screenAspect = screenW / screenH;
|
||||
float videoAspect = videoW / videoH;
|
||||
float imgAspect = imgW / imgH;
|
||||
ImVec2 uv0(0.0f, 0.0f);
|
||||
ImVec2 uv1(1.0f, 1.0f);
|
||||
if (videoAspect > screenAspect) {
|
||||
float scale = screenAspect / videoAspect;
|
||||
if (imgAspect > screenAspect) {
|
||||
float scale = screenAspect / imgAspect;
|
||||
float crop = (1.0f - scale) * 0.5f;
|
||||
uv0.x = crop;
|
||||
uv1.x = 1.0f - crop;
|
||||
} else if (videoAspect < screenAspect) {
|
||||
float scale = videoAspect / screenAspect;
|
||||
} else if (imgAspect < screenAspect) {
|
||||
float scale = imgAspect / screenAspect;
|
||||
float crop = (1.0f - scale) * 0.5f;
|
||||
uv0.y = crop;
|
||||
uv1.y = 1.0f - crop;
|
||||
}
|
||||
ImDrawList* bg = ImGui::GetBackgroundDrawList();
|
||||
bg->AddImage(static_cast<ImTextureID>(static_cast<uintptr_t>(backgroundVideo.getTextureId())),
|
||||
bg->AddImage(reinterpret_cast<ImTextureID>(bgDescriptorSet),
|
||||
ImVec2(0, 0), ImVec2(screenW, screenH), uv0, uv1);
|
||||
}
|
||||
}
|
||||
|
|
@ -763,4 +761,164 @@ void AuthScreen::loadLoginInfo() {
|
|||
LOG_INFO("Login info loaded from ", path);
|
||||
}
|
||||
|
||||
static uint32_t findMemType(VkPhysicalDevice pd, uint32_t filter, VkMemoryPropertyFlags props) {
|
||||
VkPhysicalDeviceMemoryProperties mp;
|
||||
vkGetPhysicalDeviceMemoryProperties(pd, &mp);
|
||||
for (uint32_t i = 0; i < mp.memoryTypeCount; i++) {
|
||||
if ((filter & (1 << i)) && (mp.memoryTypes[i].propertyFlags & props) == props) return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool AuthScreen::loadBackgroundImage() {
|
||||
auto& app = core::Application::getInstance();
|
||||
auto* renderer = app.getRenderer();
|
||||
if (!renderer) return false;
|
||||
bgVkCtx = renderer->getVkContext();
|
||||
if (!bgVkCtx) return false;
|
||||
|
||||
std::string imgPath = "assets/krayonsignin.png";
|
||||
if (!std::filesystem::exists(imgPath))
|
||||
imgPath = (std::filesystem::current_path() / imgPath).string();
|
||||
|
||||
int channels;
|
||||
stbi_set_flip_vertically_on_load(false);
|
||||
unsigned char* data = stbi_load(imgPath.c_str(), &bgWidth, &bgHeight, &channels, 4);
|
||||
if (!data) {
|
||||
LOG_WARNING("Auth screen: failed to load background image: ", imgPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
VkDevice device = bgVkCtx->getDevice();
|
||||
VkPhysicalDevice physDevice = bgVkCtx->getPhysicalDevice();
|
||||
VkDeviceSize imageSize = static_cast<VkDeviceSize>(bgWidth) * bgHeight * 4;
|
||||
|
||||
// Staging buffer
|
||||
VkBuffer stagingBuffer;
|
||||
VkDeviceMemory stagingMemory;
|
||||
{
|
||||
VkBufferCreateInfo bufInfo{};
|
||||
bufInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
bufInfo.size = imageSize;
|
||||
bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||
bufInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
vkCreateBuffer(device, &bufInfo, nullptr, &stagingBuffer);
|
||||
|
||||
VkMemoryRequirements memReqs;
|
||||
vkGetBufferMemoryRequirements(device, stagingBuffer, &memReqs);
|
||||
VkMemoryAllocateInfo allocInfo{};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
allocInfo.allocationSize = memReqs.size;
|
||||
allocInfo.memoryTypeIndex = findMemType(physDevice, memReqs.memoryTypeBits,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
vkAllocateMemory(device, &allocInfo, nullptr, &stagingMemory);
|
||||
vkBindBufferMemory(device, stagingBuffer, stagingMemory, 0);
|
||||
|
||||
void* mapped;
|
||||
vkMapMemory(device, stagingMemory, 0, imageSize, 0, &mapped);
|
||||
memcpy(mapped, data, imageSize);
|
||||
vkUnmapMemory(device, stagingMemory);
|
||||
}
|
||||
stbi_image_free(data);
|
||||
|
||||
// Create VkImage
|
||||
{
|
||||
VkImageCreateInfo imgInfo{};
|
||||
imgInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
imgInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
imgInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
imgInfo.extent = {static_cast<uint32_t>(bgWidth), static_cast<uint32_t>(bgHeight), 1};
|
||||
imgInfo.mipLevels = 1;
|
||||
imgInfo.arrayLayers = 1;
|
||||
imgInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
imgInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
imgInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
imgInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
imgInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
vkCreateImage(device, &imgInfo, nullptr, &bgImage);
|
||||
|
||||
VkMemoryRequirements memReqs;
|
||||
vkGetImageMemoryRequirements(device, bgImage, &memReqs);
|
||||
VkMemoryAllocateInfo allocInfo{};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
allocInfo.allocationSize = memReqs.size;
|
||||
allocInfo.memoryTypeIndex = findMemType(physDevice, memReqs.memoryTypeBits,
|
||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
vkAllocateMemory(device, &allocInfo, nullptr, &bgMemory);
|
||||
vkBindImageMemory(device, bgImage, bgMemory, 0);
|
||||
}
|
||||
|
||||
// Transfer
|
||||
bgVkCtx->immediateSubmit([&](VkCommandBuffer cmd) {
|
||||
VkImageMemoryBarrier barrier{};
|
||||
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = bgImage;
|
||||
barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
barrier.srcAccessMask = 0;
|
||||
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
|
||||
|
||||
VkBufferImageCopy region{};
|
||||
region.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
||||
region.imageExtent = {static_cast<uint32_t>(bgWidth), static_cast<uint32_t>(bgHeight), 1};
|
||||
vkCmdCopyBufferToImage(cmd, stagingBuffer, bgImage,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
||||
|
||||
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
|
||||
});
|
||||
|
||||
vkDestroyBuffer(device, stagingBuffer, nullptr);
|
||||
vkFreeMemory(device, stagingMemory, nullptr);
|
||||
|
||||
// Image view
|
||||
{
|
||||
VkImageViewCreateInfo viewInfo{};
|
||||
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
viewInfo.image = bgImage;
|
||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
vkCreateImageView(device, &viewInfo, nullptr, &bgImageView);
|
||||
}
|
||||
|
||||
// Sampler
|
||||
{
|
||||
VkSamplerCreateInfo samplerInfo{};
|
||||
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
||||
samplerInfo.magFilter = VK_FILTER_LINEAR;
|
||||
samplerInfo.minFilter = VK_FILTER_LINEAR;
|
||||
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
vkCreateSampler(device, &samplerInfo, nullptr, &bgSampler);
|
||||
}
|
||||
|
||||
bgDescriptorSet = ImGui_ImplVulkan_AddTexture(bgSampler, bgImageView,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
|
||||
LOG_INFO("Auth screen background loaded: ", bgWidth, "x", bgHeight);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AuthScreen::destroyBackgroundImage() {
|
||||
if (!bgVkCtx) return;
|
||||
VkDevice device = bgVkCtx->getDevice();
|
||||
vkDeviceWaitIdle(device);
|
||||
if (bgDescriptorSet) { ImGui_ImplVulkan_RemoveTexture(bgDescriptorSet); bgDescriptorSet = VK_NULL_HANDLE; }
|
||||
if (bgSampler) { vkDestroySampler(device, bgSampler, nullptr); bgSampler = VK_NULL_HANDLE; }
|
||||
if (bgImageView) { vkDestroyImageView(device, bgImageView, nullptr); bgImageView = VK_NULL_HANDLE; }
|
||||
if (bgImage) { vkDestroyImage(device, bgImage, nullptr); bgImage = VK_NULL_HANDLE; }
|
||||
if (bgMemory) { vkFreeMemory(device, bgMemory, nullptr); bgMemory = VK_NULL_HANDLE; }
|
||||
}
|
||||
|
||||
}} // namespace wowee::ui
|
||||
|
|
|
|||
|
|
@ -261,6 +261,21 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
|||
}
|
||||
}
|
||||
|
||||
// Apply saved MSAA setting once when renderer is available
|
||||
if (!msaaSettingsApplied_ && pendingAntiAliasing > 0) {
|
||||
auto* renderer = core::Application::getInstance().getRenderer();
|
||||
if (renderer) {
|
||||
static const VkSampleCountFlagBits aaSamples[] = {
|
||||
VK_SAMPLE_COUNT_1_BIT, VK_SAMPLE_COUNT_2_BIT,
|
||||
VK_SAMPLE_COUNT_4_BIT, VK_SAMPLE_COUNT_8_BIT
|
||||
};
|
||||
renderer->setMsaaSamples(aaSamples[pendingAntiAliasing]);
|
||||
msaaSettingsApplied_ = true;
|
||||
}
|
||||
} else {
|
||||
msaaSettingsApplied_ = true;
|
||||
}
|
||||
|
||||
// Apply auto-loot setting to GameHandler every frame (cheap bool sync)
|
||||
gameHandler.setAutoLoot(pendingAutoLoot);
|
||||
|
||||
|
|
@ -5866,6 +5881,17 @@ void GameScreen::renderSettingsWindow() {
|
|||
if (renderer) renderer->setShadowsEnabled(pendingShadows);
|
||||
saveSettings();
|
||||
}
|
||||
{
|
||||
const char* aaLabels[] = { "Off", "2x MSAA", "4x MSAA", "8x MSAA" };
|
||||
if (ImGui::Combo("Anti-Aliasing", &pendingAntiAliasing, aaLabels, 4)) {
|
||||
static const VkSampleCountFlagBits aaSamples[] = {
|
||||
VK_SAMPLE_COUNT_1_BIT, VK_SAMPLE_COUNT_2_BIT,
|
||||
VK_SAMPLE_COUNT_4_BIT, VK_SAMPLE_COUNT_8_BIT
|
||||
};
|
||||
if (renderer) renderer->setMsaaSamples(aaSamples[pendingAntiAliasing]);
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
if (ImGui::SliderInt("Ground Clutter Density", &pendingGroundClutterDensity, 0, 150, "%d%%")) {
|
||||
if (renderer) {
|
||||
if (auto* tm = renderer->getTerrainManager()) {
|
||||
|
|
@ -5896,11 +5922,13 @@ void GameScreen::renderSettingsWindow() {
|
|||
pendingVsync = kDefaultVsync;
|
||||
pendingShadows = kDefaultShadows;
|
||||
pendingGroundClutterDensity = kDefaultGroundClutterDensity;
|
||||
pendingAntiAliasing = 0;
|
||||
pendingResIndex = defaultResIndex;
|
||||
window->setFullscreen(pendingFullscreen);
|
||||
window->setVsync(pendingVsync);
|
||||
window->applyResolution(kResolutions[pendingResIndex][0], kResolutions[pendingResIndex][1]);
|
||||
if (renderer) renderer->setShadowsEnabled(pendingShadows);
|
||||
if (renderer) renderer->setMsaaSamples(VK_SAMPLE_COUNT_1_BIT);
|
||||
if (renderer) {
|
||||
if (auto* tm = renderer->getTerrainManager()) {
|
||||
tm->setGroundClutterDensityScale(static_cast<float>(pendingGroundClutterDensity) / 100.0f);
|
||||
|
|
@ -6831,6 +6859,7 @@ void GameScreen::saveSettings() {
|
|||
// Gameplay
|
||||
out << "auto_loot=" << (pendingAutoLoot ? 1 : 0) << "\n";
|
||||
out << "ground_clutter_density=" << pendingGroundClutterDensity << "\n";
|
||||
out << "antialiasing=" << pendingAntiAliasing << "\n";
|
||||
|
||||
// Controls
|
||||
out << "mouse_sensitivity=" << pendingMouseSensitivity << "\n";
|
||||
|
|
@ -6908,6 +6937,7 @@ void GameScreen::loadSettings() {
|
|||
// Gameplay
|
||||
else if (key == "auto_loot") pendingAutoLoot = (std::stoi(val) != 0);
|
||||
else if (key == "ground_clutter_density") pendingGroundClutterDensity = std::clamp(std::stoi(val), 0, 150);
|
||||
else if (key == "antialiasing") pendingAntiAliasing = std::clamp(std::stoi(val), 0, 3);
|
||||
// Controls
|
||||
else if (key == "mouse_sensitivity") pendingMouseSensitivity = std::clamp(std::stof(val), 0.05f, 1.0f);
|
||||
else if (key == "invert_mouse") pendingInvertMouse = (std::stoi(val) != 0);
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ bool UIManager::initialize(core::Window* win) {
|
|||
initInfo.MinImageCount = 2;
|
||||
initInfo.ImageCount = vkCtx->getSwapchainImageCount();
|
||||
initInfo.PipelineInfoMain.RenderPass = vkCtx->getImGuiRenderPass();
|
||||
initInfo.PipelineInfoMain.MSAASamples = VK_SAMPLE_COUNT_1_BIT;
|
||||
initInfo.PipelineInfoMain.MSAASamples = vkCtx->getMsaaSamples();
|
||||
|
||||
ImGui_ImplVulkan_Init(&initInfo);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue