mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 08:15:59 -10:00
Vulkan: upload a mission's environment map as a cubemap (#7934)
Under Vulkan a mission's $Environment Map was never uploaded: the irradiance and lighting passes only use ENVMAP when its texture slot already has an image view, and nothing created one for a bitmap that was only bm_load'ed. OpenGL uploads it the first time it binds it as a cubemap. Add VulkanTextureManager::ensureCubemapUploaded(), called from both passes. It locks the map the way OpenGL does for TCACHE_TYPE_CUBEMAP. The lock uploads through bm_data(), which now sends uncompressed DDS cubemaps (bm_is_compressed() returns 0 for them; comp_type identifies them) to uploadCubemap() as DXT ones already went. If the slot still holds no cube after the lock, because the data was already resident, it uploads it directly. A map that cannot be used is remembered by bitmap signature (handles are reused across missions) and warned about once in debug builds, and the irradiance pass is skipped instead of sampling a non-cube view. uploadCubemap() also assumed uncompressed cubemaps had one mip level and read the six faces back to back. DDS stores face0[mip0..mipN], face1[...], so faces 1-5 read the tail of face 0's mip chain. It now uses the file's mip count, expands 24-bit data over the whole chain, generates a chain for a one-level uncompressed cubemap as OpenGL does (the lighting pass samples by roughness), copies mip 0 for every array layer, cleans up the image when the staging map fails, and clears isCubemap as soon as it retires the old image. Co-authored-by: Dustinspace217 <[email protected]>
This commit is contained in:
1 parent
030bbaa793
commit
0b42d0b6e7
4 files changed
+94
-12
No files matched your search
@@ -613,6 +613,9 @@ void vulkan_calculate_irrmap()
|
||||
}
|
||||
|
||||
// Get envmap cubemap view
|
||||
if (!texManager->ensureCubemapUploaded(ENVMAP)) {
|
||||
return;
|
||||
}
|
||||
auto* envSlot = bm_get_slot(ENVMAP, true);
|
||||
if (!envSlot || !envSlot->gr_info) {
|
||||
return;
|
||||
|
||||
@@ -486,6 +486,7 @@ void VulkanDeferredLighting::render(vk::CommandBuffer cmd)
|
||||
tcache_slot_vulkan* envMapSlot = nullptr;
|
||||
tcache_slot_vulkan* irrMapSlot = nullptr;
|
||||
if (envMapAvailable) {
|
||||
texMgr->ensureCubemapUploaded(ENVMAP);
|
||||
envMapSlot = texMgr->getTextureSlot(ENVMAP);
|
||||
if (!envMapSlot || !envMapSlot->imageView || !envMapSlot->isCubemap) {
|
||||
envMapAvailable = false;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "gr_vulkan.h"
|
||||
|
||||
#include "graphics/util/pixel_swizzle.h"
|
||||
#include "bmpman/bm_internal.h"
|
||||
#include "bmpman/bmpman.h"
|
||||
#include "ddsutils/ddsutils.h"
|
||||
#include "ddsutils/bcdec.h"
|
||||
@@ -821,15 +822,15 @@ bool VulkanTextureManager::uploadCubemap(int handle, bitmap* bm, int compType)
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t mipLevels = 1;
|
||||
size_t blockSize = 0;
|
||||
|
||||
if (isCompressed) {
|
||||
blockSize = get_compressed_block_size(baseCompType);
|
||||
mipLevels = static_cast<uint32_t>(bm_get_num_mipmaps(handle));
|
||||
mipLevels = std::max<uint32_t>(mipLevels, 1);
|
||||
}
|
||||
|
||||
// uncompressed DDS cubemaps also store every face's mip chain (face0[mips], face1[mips], ...)
|
||||
auto mipLevels = static_cast<uint32_t>(bm_get_num_mipmaps(handle));
|
||||
mipLevels = std::max<uint32_t>(mipLevels, 1);
|
||||
|
||||
// A cubemap is six array layers; each face has the same layout.
|
||||
TextureUploadLayout layout;
|
||||
layout.width = faceW;
|
||||
@@ -842,6 +843,19 @@ bool VulkanTextureManager::uploadCubemap(int handle, bitmap* bm, int compType)
|
||||
size_t perFaceSize = layerByteSize(layout);
|
||||
size_t totalDataSize = perFaceSize * 6;
|
||||
|
||||
// OpenGL builds the chain for one-level cubemaps, and the env map is sampled at a roughness-based mip
|
||||
uint32_t imageMipLevels = mipLevels;
|
||||
bool generateMips = false;
|
||||
if (!isCompressed && mipLevels == 1) {
|
||||
const auto fmtProps = m_physicalDevice.getFormatProperties(format);
|
||||
if ((fmtProps.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear) &&
|
||||
(fmtProps.optimalTilingFeatures & vk::FormatFeatureFlagBits::eBlitSrc) &&
|
||||
(fmtProps.optimalTilingFeatures & vk::FormatFeatureFlagBits::eBlitDst)) {
|
||||
imageMipLevels = calculateMipLevels(faceW, faceH);
|
||||
generateMips = imageMipLevels > 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Defer destruction of existing resources
|
||||
if (ts->image) {
|
||||
auto* deletionQueue = getDeletionQueue();
|
||||
@@ -853,10 +867,14 @@ bool VulkanTextureManager::uploadCubemap(int handle, bitmap* bm, int compType)
|
||||
ts->image = nullptr;
|
||||
ts->allocation = VulkanAllocation{};
|
||||
}
|
||||
ts->isCubemap = false;
|
||||
|
||||
// Create cubemap image (6 layers, eCubeCompatible)
|
||||
vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled;
|
||||
if (!createImage(faceW, faceH, mipLevels, format, vk::ImageTiling::eOptimal,
|
||||
if (generateMips) {
|
||||
usage |= vk::ImageUsageFlagBits::eTransferSrc;
|
||||
}
|
||||
if (!createImage(faceW, faceH, imageMipLevels, format, vk::ImageTiling::eOptimal,
|
||||
usage, MemoryUsage::GpuOnly, ts->image, ts->allocation, 6, true)) {
|
||||
nprintf(("vulkan", "VulkanTexture: uploadCubemap: failed to create cubemap image\n"));
|
||||
return false;
|
||||
@@ -864,7 +882,7 @@ bool VulkanTextureManager::uploadCubemap(int handle, bitmap* bm, int compType)
|
||||
|
||||
// Create cubemap image view (samplerCube)
|
||||
ts->imageView = createImageView(ts->image, format, vk::ImageAspectFlagBits::eColor,
|
||||
mipLevels, ImageViewType::Cube, 6);
|
||||
imageMipLevels, ImageViewType::Cube, 6);
|
||||
if (!ts->imageView) {
|
||||
nprintf(("vulkan", "VulkanTexture: uploadCubemap: failed to create cube image view\n"));
|
||||
m_device.destroyImage(ts->image);
|
||||
@@ -889,15 +907,20 @@ bool VulkanTextureManager::uploadCubemap(int handle, bitmap* bm, int compType)
|
||||
if (!mapped) {
|
||||
m_memoryManager->freeAllocation(stagingAllocation);
|
||||
m_device.destroyBuffer(stagingBuffer);
|
||||
m_device.destroyImageView(ts->imageView);
|
||||
ts->imageView = nullptr;
|
||||
m_device.destroyImage(ts->image);
|
||||
ts->image = nullptr;
|
||||
m_memoryManager->freeAllocation(ts->allocation);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy data to staging buffer
|
||||
// DDS cubemap data layout: face0[mip0..mipN], face1[mip0..mipN], ..., face5[mip0..mipN]
|
||||
if (!isCompressed && bm->bpp == 24) {
|
||||
// Convert BGR to BGRA for all 6 faces
|
||||
// Convert BGR to BGRA for all 6 faces and their mips
|
||||
graphics::util::expand_BGR_to_BGRA(reinterpret_cast<const uint8_t*>(bm->data),
|
||||
static_cast<uint8_t*>(mapped), static_cast<size_t>(faceW) * faceH * 6);
|
||||
static_cast<uint8_t*>(mapped), (perFaceSize / 4) * 6);
|
||||
} else {
|
||||
memcpy(mapped, reinterpret_cast<const void*>(bm->data), totalDataSize);
|
||||
}
|
||||
@@ -914,15 +937,18 @@ bool VulkanTextureManager::uploadCubemap(int handle, bitmap* bm, int compType)
|
||||
|
||||
// Record transitions + copy and submit async
|
||||
vk::CommandBuffer cmd = beginSingleTimeCommands();
|
||||
if (generateMips) {
|
||||
copyRegions.clear();
|
||||
}
|
||||
recordUploadCommands(cmd, ts->image, stagingBuffer, format, faceW, faceH,
|
||||
mipLevels, vk::ImageLayout::eUndefined, false, copyRegions, 6);
|
||||
imageMipLevels, vk::ImageLayout::eUndefined, generateMips, copyRegions, 6);
|
||||
submitUploadAsync(cmd, stagingBuffer, stagingAllocation);
|
||||
|
||||
// Update slot info
|
||||
ts->width = faceW;
|
||||
ts->height = faceH;
|
||||
ts->format = format;
|
||||
ts->mipLevels = mipLevels;
|
||||
ts->mipLevels = imageMipLevels;
|
||||
ts->bpp = bm->bpp;
|
||||
ts->arrayLayers = 6;
|
||||
ts->bitmapHandle = handle;
|
||||
@@ -933,7 +959,7 @@ bool VulkanTextureManager::uploadCubemap(int handle, bitmap* bm, int compType)
|
||||
ts->vScale = 1.0f;
|
||||
|
||||
nprintf(("vulkan", "VulkanTexture: Cubemap uploaded: %ux%u, %u mips, format=%d\n",
|
||||
faceW, faceH, mipLevels, static_cast<int>(format)));
|
||||
faceW, faceH, imageMipLevels, static_cast<int>(format)));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1107,6 +1133,10 @@ bool VulkanTextureManager::bm_data(int handle, bitmap* bm, int compType)
|
||||
isCubemapUpload = (compType == DDS_CUBEMAP_DXT1 || compType == DDS_CUBEMAP_DXT3 ||
|
||||
compType == DDS_CUBEMAP_DXT5);
|
||||
}
|
||||
// bm_is_compressed() returns 0 for uncompressed DDS cubemaps; only comp_type identifies them
|
||||
if (!isCubemapUpload) {
|
||||
isCubemapUpload = bm_get_entry(handle)->comp_type == BM_TYPE_CUBEMAP_DDS;
|
||||
}
|
||||
|
||||
if (isCubemapUpload) {
|
||||
return uploadCubemap(handle, bm, compType);
|
||||
@@ -2205,6 +2235,46 @@ tcache_slot_vulkan* VulkanTextureManager::getTextureSlot(int handle)
|
||||
return static_cast<tcache_slot_vulkan*>(slot->gr_info);
|
||||
}
|
||||
|
||||
bool VulkanTextureManager::ensureCubemapUploaded(int handle)
|
||||
{
|
||||
if (handle < 0) {
|
||||
return false;
|
||||
}
|
||||
auto* ts = getTextureSlot(handle);
|
||||
if (ts != nullptr && ts->imageView && ts->isCubemap) {
|
||||
return true;
|
||||
}
|
||||
// one warning and no per-frame retry for a map that cannot be used; handles are reused, signatures are not
|
||||
const uint signature = bm_get_entry(handle)->signature;
|
||||
if (signature == m_failedCubemapSignature) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool uploaded = false;
|
||||
// the cubemap kind lives in comp_type; type is just BM_TYPE_DDS
|
||||
const BM_TYPE type = bm_get_entry(handle)->comp_type;
|
||||
if (type == BM_TYPE_CUBEMAP_DDS || type == BM_TYPE_CUBEMAP_DXT1 || type == BM_TYPE_CUBEMAP_DXT3 ||
|
||||
type == BM_TYPE_CUBEMAP_DXT5) {
|
||||
// same lock as OpenGL's opengl_determine_bpp_and_flags for TCACHE_TYPE_CUBEMAP; DXT data stays compressed
|
||||
const int compType = bm_is_compressed(handle);
|
||||
const int bpp = bm_has_alpha_channel(handle) ? 32 : 24;
|
||||
// bm_lock() uploads through bm_data(); upload here only if that did not leave a cube
|
||||
bitmap* bmp = bm_lock(handle, static_cast<ubyte>(bpp), BMP_TEX_OTHER);
|
||||
if (bmp != nullptr) {
|
||||
ts = getTextureSlot(handle);
|
||||
uploaded = (ts != nullptr && ts->imageView && ts->isCubemap) || uploadCubemap(handle, bmp, compType);
|
||||
bm_unlock(handle);
|
||||
}
|
||||
}
|
||||
|
||||
if (!uploaded) {
|
||||
m_failedCubemapSignature = signature;
|
||||
Warning(LOCATION, "Environment map '%s' could not be uploaded as a cubemap; reflections are off.",
|
||||
bm_get_filename(handle));
|
||||
}
|
||||
return uploaded;
|
||||
}
|
||||
|
||||
vk::Format VulkanTextureManager::bppToVkFormat(int bpp, bool compressed, int compressionType)
|
||||
{
|
||||
if (compressed) {
|
||||
@@ -2750,7 +2820,7 @@ void VulkanTextureManager::recordUploadCommands(vk::CommandBuffer cmd, vk::Image
|
||||
region.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
region.imageSubresource.mipLevel = 0;
|
||||
region.imageSubresource.baseArrayLayer = 0;
|
||||
region.imageSubresource.layerCount = 1;
|
||||
region.imageSubresource.layerCount = arrayLayers;
|
||||
region.imageOffset = vk::Offset3D(0, 0, 0);
|
||||
region.imageExtent = vk::Extent3D(width, height, 1);
|
||||
|
||||
|
||||
@@ -194,6 +194,13 @@ public:
|
||||
*/
|
||||
tcache_slot_vulkan* getTextureSlot(int handle);
|
||||
|
||||
/**
|
||||
* @brief Upload a cubemap bitmap as a cubemap if it has not been uploaded yet
|
||||
* @param[in] handle bitmap handle of a DDS cubemap (compressed or not) or a cubemap render target
|
||||
* @return true if the handle now has a cubemap image view
|
||||
*/
|
||||
bool ensureCubemapUploaded(int handle);
|
||||
|
||||
/**
|
||||
* @brief Bitmap handle of the render target currently bound via bm_set_render_target,
|
||||
* or -1 when rendering to the default framebuffer. Used by gr.screenToBlob so a
|
||||
@@ -382,6 +389,7 @@ private:
|
||||
|
||||
vk::Device m_device;
|
||||
vk::PhysicalDevice m_physicalDevice;
|
||||
uint m_failedCubemapSignature = 0;
|
||||
VulkanMemoryManager* m_memoryManager = nullptr;
|
||||
vk::CommandPool m_commandPool;
|
||||
vk::Queue m_graphicsQueue;
|
||||
|
||||
Reference in new issue
Block a user