Files
MinecraftCommunityEdition-c…/Minecraft.Client/Windows64/C4JRender_Vulkan.cpp
kit rae abc5fae7f2 Revert "texture and alpha shader paths implemented"
This reverts commit c92ae80591bdac09feb49b0098a4850c273f562a
2026-03-05 22:41:50 -06:00

4609 lines
166 KiB
C++

// ============================================================================
// C4JRender_Vulkan.cpp - Windows Vulkan backend for C4JRender.
// ============================================================================
#include "stdafx.h"
#define VK_USE_PLATFORM_WIN32_KHR
#include <vulkan/vulkan.h>
#include <wincodec.h>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <cstdint>
#include <memory>
#include <mutex>
#include <unordered_map>
#include <vector>
#include "VulkanTriangleShaders.inl"
#include "VulkanUIShaders.inl"
#include "VulkanUIBridge.h"
// ---- Global singleton ----
C4JRender RenderManager;
// ============================================================================
// Internal state
// ============================================================================
static const int MATRIX_STACK_DEPTH = 32;
static const int NUM_MATRIX_MODES = 3; // modelview, projection, texture
struct MatrixStack {
float stack[MATRIX_STACK_DEPTH][16];
int top;
};
static thread_local MatrixStack g_matStacks[NUM_MATRIX_MODES];
static thread_local int g_curMatrixMode = 0; // GL_MODELVIEW
static thread_local bool g_matrixDirty = true;
static thread_local bool g_matrixStacksInitialised = false;
static void ensureThreadLocalMatrixStacksInitialised() {
if (g_matrixStacksInitialised)
return;
for (int i = 0; i < NUM_MATRIX_MODES; ++i) {
std::memset(g_matStacks[i].stack[0], 0, 16 * sizeof(float));
g_matStacks[i].stack[0][0] = 1.0f;
g_matStacks[i].stack[0][5] = 1.0f;
g_matStacks[i].stack[0][10] = 1.0f;
g_matStacks[i].stack[0][15] = 1.0f;
g_matStacks[i].top = 0;
}
g_curMatrixMode = 0;
g_matrixDirty = true;
g_matrixStacksInitialised = true;
}
// Vulkan objects
static VkInstance g_vkInstance = VK_NULL_HANDLE;
static VkPhysicalDevice g_vkPhysicalDevice = VK_NULL_HANDLE;
static VkDevice g_vkDevice = VK_NULL_HANDLE;
static VkQueue g_vkGraphicsQueue = VK_NULL_HANDLE;
static VkSurfaceKHR g_vkSurface = VK_NULL_HANDLE;
static HWND g_vkWindow = NULL;
static VkSwapchainKHR g_vkSwapchain = VK_NULL_HANDLE;
static VkRenderPass g_vkRenderPass = VK_NULL_HANDLE;
static VkPipelineLayout g_vkPipelineLayout = VK_NULL_HANDLE;
static VkPipeline g_vkTrianglePipeline = VK_NULL_HANDLE;
static std::unordered_map<uint64_t, VkPipeline> g_vkTrianglePipelines;
static VkDescriptorSetLayout g_vkTriangleDescriptorSetLayout = VK_NULL_HANDLE;
static VkDescriptorPool g_vkTriangleDescriptorPool = VK_NULL_HANDLE;
static VkDescriptorSetLayout g_vkUiDescriptorSetLayout = VK_NULL_HANDLE;
static VkDescriptorPool g_vkUiDescriptorPool = VK_NULL_HANDLE;
static VkDescriptorSet g_vkUiDescriptorSet = VK_NULL_HANDLE;
static VkPipelineLayout g_vkUiPipelineLayout = VK_NULL_HANDLE;
static VkPipeline g_vkUiPipeline = VK_NULL_HANDLE;
static VkCommandPool g_vkCommandPool = VK_NULL_HANDLE;
static VkCommandBuffer g_vkCommandBuffer = VK_NULL_HANDLE;
static VkFence g_vkFence = VK_NULL_HANDLE;
static VkSemaphore g_vkImageAvailable = VK_NULL_HANDLE;
static VkSemaphore g_vkRenderFinished = VK_NULL_HANDLE;
static uint32_t g_vkGraphicsFamily = 0;
static std::vector<VkImage> g_vkSwapImages;
static std::vector<VkImageView> g_vkSwapImageViews;
static std::vector<VkFramebuffer> g_vkFramebuffers;
static std::vector<VkImageLayout> g_vkSwapImageLayouts;
static VkFormat g_vkSwapFormat = VK_FORMAT_B8G8R8A8_UNORM;
static VkExtent2D g_vkSwapExtent = {0, 0};
static VkImage g_vkDepthImage = VK_NULL_HANDLE;
static VkDeviceMemory g_vkDepthMemory = VK_NULL_HANDLE;
static VkImageView g_vkDepthImageView = VK_NULL_HANDLE;
static VkFormat g_vkDepthFormat = VK_FORMAT_UNDEFINED;
static VkBuffer g_vkDynamicVertexBuffer = VK_NULL_HANDLE;
static VkDeviceMemory g_vkDynamicVertexMemory = VK_NULL_HANDLE;
static size_t g_vkDynamicVertexCapacity = 0;
static bool g_vkDynamicVertexHostCoherent = true;
static uint8_t *g_vkDynamicVertexMapped = nullptr;
static VkBuffer g_vkUiStagingBuffer = VK_NULL_HANDLE;
static VkDeviceMemory g_vkUiStagingMemory = VK_NULL_HANDLE;
static size_t g_vkUiStagingCapacity = 0;
static bool g_vkUiStagingHostCoherent = true;
static uint8_t *g_vkUiStagingMapped = nullptr;
static VkImage g_vkUiImage = VK_NULL_HANDLE;
static VkDeviceMemory g_vkUiImageMemory = VK_NULL_HANDLE;
static VkImageView g_vkUiImageView = VK_NULL_HANDLE;
static VkSampler g_vkUiSampler = VK_NULL_HANDLE;
static VkImageLayout g_vkUiImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
static uint32_t g_vkUiImageWidth = 0;
static uint32_t g_vkUiImageHeight = 0;
static bool g_vkUiImageReady = false;
static bool g_vkInitialized = false;
static int g_nextCommandBufferId = 1;
static int g_nextTextureId = 1;
static const uint32_t kVertexStridePF3TF2CB4NB4XW1 = 32;
static const uint32_t kMaxTriangleTextureDescriptors = 4096;
struct TrianglePushConstants {
float mvp[16];
float alphaState[4]; // x=enable, y=ref, z=func, w=unused
};
struct VulkanTextureLevelData {
uint32_t width;
uint32_t height;
std::vector<uint8_t> pixels;
bool valid;
};
struct VulkanTexture {
int id;
uint32_t width;
uint32_t height;
uint32_t mipLevels;
VkImage image;
VkDeviceMemory memory;
VkImageView imageView;
VkSampler sampler;
VkDescriptorSet descriptorSet;
VkImageLayout imageLayout;
int minFilter;
int magFilter;
int wrapS;
int wrapT;
uint32_t requestedMipLevels;
bool pendingUpload;
std::vector<VulkanTextureLevelData> levelData;
};
static std::unordered_map<int, VulkanTexture> g_vkTextures;
static VulkanTexture g_vkWhiteTexture = {};
static bool g_vkWhiteTextureReady = false;
struct VulkanQueuedDraw {
uint32_t firstVertex;
uint32_t vertexCount;
float mvp[16];
bool depthTestEnable;
bool depthWriteEnable;
VkCompareOp depthCompareOp;
bool blendEnable;
VkBlendFactor srcBlendFactor;
VkBlendFactor dstBlendFactor;
VkColorComponentFlags colorWriteMask;
bool cullEnable;
bool cullClockwise;
float blendConstants[4];
VkDescriptorSet descriptorSet;
bool alphaTestEnable;
int alphaFunc;
float alphaRef;
};
static std::vector<uint8_t> g_vkFrameVertexData;
static std::vector<VulkanQueuedDraw> g_vkQueuedDraws;
static bool g_vkShowCornerOverlay = true;
static bool g_vkShowPerfOverlay = true;
static LARGE_INTEGER g_vkPerfFreq = {};
static LARGE_INTEGER g_vkPerfLastPresent = {};
static float g_vkPerfFpsSmoothed = 0.0f;
static uint32_t g_vkPerfDrawCount = 0;
static uint32_t g_vkPerfVertexCount = 0;
static uint32_t g_vkPerfUploadBytes = 0;
static size_t g_vkFrameVertexReserveBytes = 1u << 20;
static size_t g_vkFrameDrawReserveCount = 4096;
struct VulkanUiUploadFrame {
std::vector<uint8_t> pixelsBGRA;
uint32_t width;
uint32_t height;
bool pending;
};
static VulkanUiUploadFrame g_vkUiUpload = {};
static bool updateUiDescriptorSet();
static void destroyAllTextures(bool preserveCpuCache);
struct RecordedDrawCall {
C4JRender::ePrimitiveType primitiveType;
int count;
C4JRender::eVertexType vType;
C4JRender::ePixelShaderType psType;
std::vector<uint8_t> vertexData;
// Pre-expanded to BootstrapVertex layout (RGBA + triangle list) for fast replay.
std::vector<uint8_t> preparedVertexData;
uint32_t preparedVertexCount;
bool hasLocalModelMatrix;
float localModelMatrix[16];
bool useCapturedState;
bool depthTestEnable;
bool depthWriteEnable;
VkCompareOp depthCompareOp;
bool blendEnable;
VkBlendFactor srcBlendFactor;
VkBlendFactor dstBlendFactor;
VkColorComponentFlags colorWriteMask;
bool cullEnable;
bool cullClockwise;
float blendConstants[4];
// texture state is tracked separately; replay should not clobber runtime binds.
bool captureTextureState;
int textureId;
// same idea for alpha test state.
bool captureAlphaState;
bool alphaTestEnable;
int alphaFunc;
float alphaRef;
};
static std::unordered_map<int, std::shared_ptr<std::vector<RecordedDrawCall>>>
g_vkCommandLists;
static std::mutex g_vkCommandListsMutex;
static thread_local bool g_vkIsRecordingCommandList = false;
static thread_local int g_vkRecordingCommandListIndex = -1;
static thread_local std::vector<RecordedDrawCall> g_vkRecordingScratch;
static thread_local bool g_vkRecordingHasStateChanges = false;
static thread_local bool g_vkRecordingHasTextureStateChanges = false;
static thread_local bool g_vkRecordingHasAlphaStateChanges = false;
static thread_local bool g_vkRecordingBaseModelValid = false;
static thread_local float g_vkRecordingBaseModelInv[16];
struct BootstrapVertex {
float x, y, z;
float u, v;
uint8_t r, g, b, a;
uint8_t nx, ny, nz, nw;
uint32_t pad;
};
static_assert(sizeof(BootstrapVertex) == kVertexStridePF3TF2CB4NB4XW1,
"BootstrapVertex must be 32 bytes.");
static float g_clearColour[4] = {0.0f, 0.0f, 0.0f, 1.0f};
static bool g_isWidescreen = true;
static thread_local bool g_vkStateDepthTestEnable = true;
static thread_local bool g_vkStateDepthWriteEnable = true;
static thread_local VkCompareOp g_vkStateDepthCompareOp =
VK_COMPARE_OP_LESS_OR_EQUAL;
static thread_local bool g_vkStateBlendEnable = false;
static thread_local VkBlendFactor g_vkStateSrcBlendFactor = VK_BLEND_FACTOR_ONE;
static thread_local VkBlendFactor g_vkStateDstBlendFactor = VK_BLEND_FACTOR_ZERO;
static thread_local bool g_vkStateCullEnable = false;
static thread_local bool g_vkStateCullClockwise = true;
static thread_local VkColorComponentFlags g_vkStateColorWriteMask =
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
static thread_local float g_vkStateColour[4] = {1.0f, 1.0f, 1.0f, 1.0f};
static thread_local float g_vkStateBlendConstants[4] = {1.0f, 1.0f, 1.0f,
1.0f};
static thread_local int g_vkStateTextureId = -1;
static thread_local int g_vkStateVertexTextureId = -1;
static thread_local bool g_vkStateAlphaTestEnable = false;
static thread_local int g_vkStateAlphaFunc = GL_ALWAYS;
static thread_local float g_vkStateAlphaRef = 0.0f;
static int g_vkPendingTextureLevels = 1;
static void debugVk(const char *msg) { OutputDebugStringA(msg); }
static bool getWindowClientSize(HWND hWnd, int &width, int &height) {
width = 0;
height = 0;
if (hWnd == NULL)
return false;
RECT rect = {};
if (!GetClientRect(hWnd, &rect))
return false;
width = rect.right - rect.left;
height = rect.bottom - rect.top;
return (width > 0 && height > 0);
}
static void debugVkResult(const char *what, VkResult result) {
char buffer[256];
std::snprintf(buffer, sizeof(buffer), "C4JRender_Vulkan: %s (VkResult=%d)\n",
what, (int)result);
OutputDebugStringA(buffer);
}
static uint64_t makeTrianglePipelineKey(bool depthTestEnable,
bool depthWriteEnable,
VkCompareOp depthCompareOp,
bool blendEnable,
VkBlendFactor srcBlendFactor,
VkBlendFactor dstBlendFactor,
VkColorComponentFlags colorWriteMask,
bool cullEnable,
bool cullClockwise) {
uint64_t key = 0;
key |= (depthTestEnable ? 1ull : 0ull) << 0;
key |= (depthWriteEnable ? 1ull : 0ull) << 1;
key |= (blendEnable ? 1ull : 0ull) << 2;
key |= (cullEnable ? 1ull : 0ull) << 3;
key |= (cullClockwise ? 1ull : 0ull) << 4;
key |= (static_cast<uint64_t>(depthCompareOp) & 0xffull) << 8;
key |= (static_cast<uint64_t>(srcBlendFactor) & 0xffull) << 16;
key |= (static_cast<uint64_t>(dstBlendFactor) & 0xffull) << 24;
key |= (static_cast<uint64_t>(colorWriteMask) & 0xffull) << 32;
return key;
}
static VkCompareOp mapDepthFuncToVk(int func) {
switch (func) {
case 1:
return VK_COMPARE_OP_NEVER;
case 2:
return VK_COMPARE_OP_LESS;
case 3:
return VK_COMPARE_OP_EQUAL;
case 4:
return VK_COMPARE_OP_LESS_OR_EQUAL;
case 5:
return VK_COMPARE_OP_GREATER;
case 6:
return VK_COMPARE_OP_NOT_EQUAL;
case 7:
return VK_COMPARE_OP_GREATER_OR_EQUAL;
case 8:
return VK_COMPARE_OP_ALWAYS;
default:
return VK_COMPARE_OP_LESS_OR_EQUAL;
}
}
static VkBlendFactor mapBlendFactorToVk(int factor) {
switch (factor) {
case 1:
return VK_BLEND_FACTOR_ZERO;
case 2:
return VK_BLEND_FACTOR_ONE;
case 3:
return VK_BLEND_FACTOR_SRC_COLOR;
case 4:
return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR;
case 5:
return VK_BLEND_FACTOR_SRC_ALPHA;
case 6:
return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
case 7:
return VK_BLEND_FACTOR_DST_ALPHA;
case 8:
return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA;
case 9:
return VK_BLEND_FACTOR_DST_COLOR;
case 10:
return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR;
case 17:
return VK_BLEND_FACTOR_CONSTANT_ALPHA;
case 18:
return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA;
default:
return VK_BLEND_FACTOR_ONE;
}
}
static void resetThreadLocalRenderState() {
g_vkStateDepthTestEnable = true;
g_vkStateDepthWriteEnable = true;
g_vkStateDepthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
g_vkStateBlendEnable = false;
g_vkStateSrcBlendFactor = VK_BLEND_FACTOR_ONE;
g_vkStateDstBlendFactor = VK_BLEND_FACTOR_ZERO;
g_vkStateCullEnable = false;
g_vkStateCullClockwise = true;
g_vkStateColorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT |
VK_COLOR_COMPONENT_A_BIT;
g_vkStateColour[0] = 1.0f;
g_vkStateColour[1] = 1.0f;
g_vkStateColour[2] = 1.0f;
g_vkStateColour[3] = 1.0f;
g_vkStateBlendConstants[0] = 1.0f;
g_vkStateBlendConstants[1] = 1.0f;
g_vkStateBlendConstants[2] = 1.0f;
g_vkStateBlendConstants[3] = 1.0f;
g_vkStateTextureId = -1;
g_vkStateVertexTextureId = -1;
g_vkStateAlphaTestEnable = false;
g_vkStateAlphaFunc = GL_ALWAYS;
g_vkStateAlphaRef = 0.0f;
}
void VulkanSubmitIggyOverlayBGRA(int width, int height, const void *pixels,
size_t bytes) {
if (width <= 0 || height <= 0 || pixels == nullptr)
return;
const size_t expectedBytes =
static_cast<size_t>(width) * static_cast<size_t>(height) * 4u;
if (bytes < expectedBytes)
return;
g_vkUiUpload.width = static_cast<uint32_t>(width);
g_vkUiUpload.height = static_cast<uint32_t>(height);
g_vkUiUpload.pixelsBGRA.resize(expectedBytes);
std::memcpy(g_vkUiUpload.pixelsBGRA.data(), pixels, expectedBytes);
g_vkUiUpload.pending = true;
}
static bool hasValidationLayer(const char *layerName) {
uint32_t layerCount = 0;
VkResult res = vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
if (res != VK_SUCCESS || layerCount == 0)
return false;
std::vector<VkLayerProperties> props(layerCount);
res = vkEnumerateInstanceLayerProperties(&layerCount, props.data());
if (res != VK_SUCCESS)
return false;
for (const auto &p : props) {
if (std::strcmp(p.layerName, layerName) == 0)
return true;
}
return false;
}
template <typename T> static void SafeReleaseCOM(T *&p) {
if (p) {
p->Release();
p = nullptr;
}
}
static std::wstring toWidePath(const char *path) {
if (!path)
return std::wstring();
int len = MultiByteToWideChar(CP_UTF8, 0, path, -1, nullptr, 0);
UINT codePage = CP_UTF8;
if (len <= 0) {
codePage = CP_ACP;
len = MultiByteToWideChar(codePage, 0, path, -1, nullptr, 0);
}
if (len <= 0)
return std::wstring();
std::wstring out(len, L'\0');
MultiByteToWideChar(codePage, 0, path, -1, &out[0], len);
if (!out.empty() && out.back() == L'\0')
out.pop_back();
return out;
}
static HRESULT decodeFrameToArgb(IWICBitmapFrameDecode *frame,
D3DXIMAGE_INFO *pSrcInfo, int **ppDataOut) {
if (!frame || !pSrcInfo || !ppDataOut)
return E_INVALIDARG;
UINT width = 0;
UINT height = 0;
HRESULT hr = frame->GetSize(&width, &height);
if (FAILED(hr) || width == 0 || height == 0)
return FAILED(hr) ? hr : E_FAIL;
IWICImagingFactory *factory = nullptr;
IWICFormatConverter *converter = nullptr;
hr = CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&factory));
if (FAILED(hr))
return hr;
hr = factory->CreateFormatConverter(&converter);
if (FAILED(hr)) {
SafeReleaseCOM(factory);
return hr;
}
hr = converter->Initialize(frame, GUID_WICPixelFormat32bppBGRA,
WICBitmapDitherTypeNone, nullptr, 0.0f,
WICBitmapPaletteTypeCustom);
if (FAILED(hr)) {
SafeReleaseCOM(converter);
SafeReleaseCOM(factory);
return hr;
}
const UINT stride = width * 4;
const UINT byteCount = stride * height;
std::vector<unsigned char> pixels(byteCount);
hr = converter->CopyPixels(nullptr, stride, byteCount, pixels.data());
if (FAILED(hr)) {
SafeReleaseCOM(converter);
SafeReleaseCOM(factory);
return hr;
}
const size_t pixelCount = static_cast<size_t>(width) * static_cast<size_t>(height);
int *out = new int[pixelCount];
for (size_t i = 0; i < pixelCount; ++i) {
const unsigned char b = pixels[i * 4 + 0];
const unsigned char g = pixels[i * 4 + 1];
const unsigned char r = pixels[i * 4 + 2];
const unsigned char a = pixels[i * 4 + 3];
out[i] = (static_cast<int>(a) << 24) | (static_cast<int>(r) << 16) |
(static_cast<int>(g) << 8) | static_cast<int>(b);
}
pSrcInfo->Width = static_cast<int>(width);
pSrcInfo->Height = static_cast<int>(height);
*ppDataOut = out;
SafeReleaseCOM(converter);
SafeReleaseCOM(factory);
return S_OK;
}
static uint32_t findMemoryTypeIndex(uint32_t typeBits,
VkMemoryPropertyFlags requiredProperties,
bool &foundOut) {
foundOut = false;
if (g_vkPhysicalDevice == VK_NULL_HANDLE)
return 0;
VkPhysicalDeviceMemoryProperties memProps = {};
vkGetPhysicalDeviceMemoryProperties(g_vkPhysicalDevice, &memProps);
for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) {
if ((typeBits & (1u << i)) &&
(memProps.memoryTypes[i].propertyFlags & requiredProperties) ==
requiredProperties) {
foundOut = true;
return i;
}
}
return 0;
}
static bool hasStencilComponent(VkFormat format) {
return format == VK_FORMAT_D24_UNORM_S8_UINT ||
format == VK_FORMAT_D32_SFLOAT_S8_UINT;
}
static VkFormat chooseDepthFormat() {
if (g_vkPhysicalDevice == VK_NULL_HANDLE)
return VK_FORMAT_UNDEFINED;
const VkFormat candidates[] = {
VK_FORMAT_D32_SFLOAT,
VK_FORMAT_D24_UNORM_S8_UINT,
VK_FORMAT_D16_UNORM,
};
for (VkFormat format : candidates) {
VkFormatProperties props = {};
vkGetPhysicalDeviceFormatProperties(g_vkPhysicalDevice, format, &props);
if (props.optimalTilingFeatures &
VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) {
return format;
}
}
return VK_FORMAT_UNDEFINED;
}
static VkFilter mapTextureFilterToVk(int filter) {
return (filter == GL_NEAREST) ? VK_FILTER_NEAREST : VK_FILTER_LINEAR;
}
static VkSamplerAddressMode mapTextureWrapToVk(int wrap) {
return (wrap == GL_REPEAT) ? VK_SAMPLER_ADDRESS_MODE_REPEAT
: VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
}
static bool hasTextureUploadContext() {
return g_vkInitialized && g_vkDevice != VK_NULL_HANDLE &&
g_vkGraphicsQueue != VK_NULL_HANDLE &&
g_vkCommandPool != VK_NULL_HANDLE &&
g_vkTriangleDescriptorPool != VK_NULL_HANDLE &&
g_vkTriangleDescriptorSetLayout != VK_NULL_HANDLE;
}
static void cacheTextureLevelPixels(VulkanTexture &tex, uint32_t level,
uint32_t width, uint32_t height,
const void *data) {
if (data == nullptr || width == 0 || height == 0)
return;
if (tex.levelData.size() <= level) {
tex.levelData.resize(level + 1);
}
VulkanTextureLevelData &dst = tex.levelData[level];
dst.width = width;
dst.height = height;
const size_t bytes =
static_cast<size_t>(width) * static_cast<size_t>(height) * 4u;
dst.pixels.resize(bytes);
std::memcpy(dst.pixels.data(), data, bytes);
dst.valid = true;
tex.pendingUpload = true;
}
static bool patchTextureLevelPixels(VulkanTexture &tex, uint32_t level,
uint32_t xoffset, uint32_t yoffset,
uint32_t width, uint32_t height,
const void *data) {
if (data == nullptr || width == 0 || height == 0)
return false;
if (level >= tex.levelData.size())
return false;
VulkanTextureLevelData &dst = tex.levelData[level];
if (!dst.valid || dst.width == 0 || dst.height == 0)
return false;
if (xoffset + width > dst.width || yoffset + height > dst.height)
return false;
const uint8_t *src = static_cast<const uint8_t *>(data);
uint8_t *dstBase = dst.pixels.data();
const size_t srcRowBytes = static_cast<size_t>(width) * 4u;
const size_t dstRowBytes = static_cast<size_t>(dst.width) * 4u;
for (uint32_t y = 0; y < height; ++y) {
uint8_t *dstRow = dstBase +
(static_cast<size_t>(yoffset + y) * dstRowBytes) +
static_cast<size_t>(xoffset) * 4u;
const uint8_t *srcRow = src + static_cast<size_t>(y) * srcRowBytes;
std::memcpy(dstRow, srcRow, srcRowBytes);
}
tex.pendingUpload = true;
return true;
}
static bool computeTextureDimensionsFromCache(const VulkanTexture &tex,
uint32_t &baseWidthOut,
uint32_t &baseHeightOut,
uint32_t &mipLevelsOut) {
bool haveAny = false;
uint32_t baseWidth = 0;
uint32_t baseHeight = 0;
uint32_t maxLevel = 0;
for (uint32_t level = 0; level < static_cast<uint32_t>(tex.levelData.size());
++level) {
const VulkanTextureLevelData &ld = tex.levelData[level];
if (!ld.valid || ld.width == 0 || ld.height == 0)
continue;
uint32_t candidateWidth = ld.width;
uint32_t candidateHeight = ld.height;
if (level > 0) {
candidateWidth <<= level;
candidateHeight <<= level;
}
if (!haveAny) {
baseWidth = candidateWidth;
baseHeight = candidateHeight;
haveAny = true;
} else {
if (candidateWidth > baseWidth)
baseWidth = candidateWidth;
if (candidateHeight > baseHeight)
baseHeight = candidateHeight;
}
if (level > maxLevel)
maxLevel = level;
}
if (!haveAny || baseWidth == 0 || baseHeight == 0)
return false;
uint32_t levels = maxLevel + 1;
if (tex.requestedMipLevels > levels)
levels = tex.requestedMipLevels;
baseWidthOut = baseWidth;
baseHeightOut = baseHeight;
mipLevelsOut = levels;
return true;
}
static bool allocateTextureDescriptorSetIfNeeded(VulkanTexture &tex) {
if (tex.descriptorSet != VK_NULL_HANDLE)
return true;
if (g_vkDevice == VK_NULL_HANDLE ||
g_vkTriangleDescriptorPool == VK_NULL_HANDLE ||
g_vkTriangleDescriptorSetLayout == VK_NULL_HANDLE)
return false;
VkDescriptorSetAllocateInfo dsAI = {};
dsAI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
dsAI.descriptorPool = g_vkTriangleDescriptorPool;
dsAI.descriptorSetCount = 1;
dsAI.pSetLayouts = &g_vkTriangleDescriptorSetLayout;
VkResult result = vkAllocateDescriptorSets(g_vkDevice, &dsAI, &tex.descriptorSet);
if (result != VK_SUCCESS) {
debugVkResult("Failed to allocate triangle texture descriptor set", result);
tex.descriptorSet = VK_NULL_HANDLE;
return false;
}
return true;
}
static bool updateTextureDescriptorSet(VulkanTexture &tex) {
if (!allocateTextureDescriptorSetIfNeeded(tex))
return false;
if (g_vkDevice == VK_NULL_HANDLE || tex.descriptorSet == VK_NULL_HANDLE ||
tex.imageView == VK_NULL_HANDLE || tex.sampler == VK_NULL_HANDLE ||
tex.imageLayout != VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
return false;
}
VkDescriptorImageInfo imageInfo = {};
imageInfo.sampler = tex.sampler;
imageInfo.imageView = tex.imageView;
imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
VkWriteDescriptorSet write = {};
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write.dstSet = tex.descriptorSet;
write.dstBinding = 0;
write.dstArrayElement = 0;
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
write.descriptorCount = 1;
write.pImageInfo = &imageInfo;
vkUpdateDescriptorSets(g_vkDevice, 1, &write, 0, nullptr);
return true;
}
static void destroyTextureGpuResources(VulkanTexture &tex) {
if (g_vkDevice != VK_NULL_HANDLE && tex.descriptorSet != VK_NULL_HANDLE &&
g_vkTriangleDescriptorPool != VK_NULL_HANDLE) {
vkFreeDescriptorSets(g_vkDevice, g_vkTriangleDescriptorPool, 1,
&tex.descriptorSet);
}
tex.descriptorSet = VK_NULL_HANDLE;
if (g_vkDevice != VK_NULL_HANDLE && tex.sampler != VK_NULL_HANDLE) {
vkDestroySampler(g_vkDevice, tex.sampler, nullptr);
}
tex.sampler = VK_NULL_HANDLE;
if (g_vkDevice != VK_NULL_HANDLE && tex.imageView != VK_NULL_HANDLE) {
vkDestroyImageView(g_vkDevice, tex.imageView, nullptr);
}
tex.imageView = VK_NULL_HANDLE;
if (g_vkDevice != VK_NULL_HANDLE && tex.image != VK_NULL_HANDLE) {
vkDestroyImage(g_vkDevice, tex.image, nullptr);
}
tex.image = VK_NULL_HANDLE;
if (g_vkDevice != VK_NULL_HANDLE && tex.memory != VK_NULL_HANDLE) {
vkFreeMemory(g_vkDevice, tex.memory, nullptr);
}
tex.memory = VK_NULL_HANDLE;
tex.imageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
tex.width = 0;
tex.height = 0;
tex.mipLevels = 1;
}
static bool recreateTextureSampler(VulkanTexture &tex) {
if (g_vkDevice == VK_NULL_HANDLE)
return false;
if (tex.sampler != VK_NULL_HANDLE) {
vkDestroySampler(g_vkDevice, tex.sampler, nullptr);
tex.sampler = VK_NULL_HANDLE;
}
VkSamplerCreateInfo samplerCI = {};
samplerCI.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
samplerCI.magFilter = mapTextureFilterToVk(tex.magFilter);
samplerCI.minFilter = mapTextureFilterToVk(tex.minFilter);
samplerCI.mipmapMode =
(tex.minFilter == GL_LINEAR) ? VK_SAMPLER_MIPMAP_MODE_LINEAR
: VK_SAMPLER_MIPMAP_MODE_NEAREST;
samplerCI.addressModeU = mapTextureWrapToVk(tex.wrapS);
samplerCI.addressModeV = mapTextureWrapToVk(tex.wrapT);
samplerCI.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerCI.mipLodBias = 0.0f;
samplerCI.anisotropyEnable = VK_FALSE;
samplerCI.maxAnisotropy = 1.0f;
samplerCI.compareEnable = VK_FALSE;
samplerCI.compareOp = VK_COMPARE_OP_ALWAYS;
samplerCI.minLod = 0.0f;
samplerCI.maxLod = (tex.mipLevels > 1) ? static_cast<float>(tex.mipLevels - 1)
: 0.0f;
samplerCI.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
samplerCI.unnormalizedCoordinates = VK_FALSE;
VkResult result = vkCreateSampler(g_vkDevice, &samplerCI, nullptr, &tex.sampler);
if (result != VK_SUCCESS || tex.sampler == VK_NULL_HANDLE) {
debugVkResult("Failed to create texture sampler", result);
tex.sampler = VK_NULL_HANDLE;
return false;
}
if (tex.imageView != VK_NULL_HANDLE &&
tex.imageLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
return updateTextureDescriptorSet(tex);
}
return true;
}
static bool createTextureImageAndView(VulkanTexture &tex, uint32_t width,
uint32_t height, uint32_t mipLevels) {
if (g_vkDevice == VK_NULL_HANDLE || width == 0 || height == 0 ||
mipLevels == 0)
return false;
destroyTextureGpuResources(tex);
VkImageCreateInfo imageCI = {};
imageCI.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageCI.imageType = VK_IMAGE_TYPE_2D;
imageCI.format = VK_FORMAT_R8G8B8A8_UNORM;
imageCI.extent = {width, height, 1};
imageCI.mipLevels = mipLevels;
imageCI.arrayLayers = 1;
imageCI.samples = VK_SAMPLE_COUNT_1_BIT;
imageCI.tiling = VK_IMAGE_TILING_OPTIMAL;
imageCI.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
imageCI.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageCI.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VkResult result = vkCreateImage(g_vkDevice, &imageCI, nullptr, &tex.image);
if (result != VK_SUCCESS || tex.image == VK_NULL_HANDLE) {
debugVkResult("Failed to create texture image", result);
destroyTextureGpuResources(tex);
return false;
}
VkMemoryRequirements memReq = {};
vkGetImageMemoryRequirements(g_vkDevice, tex.image, &memReq);
bool foundMemory = false;
const uint32_t memoryTypeIndex = findMemoryTypeIndex(
memReq.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, foundMemory);
if (!foundMemory) {
debugVk("C4JRender_Vulkan: No device-local memory type for texture image.\n");
destroyTextureGpuResources(tex);
return false;
}
VkMemoryAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memReq.size;
allocInfo.memoryTypeIndex = memoryTypeIndex;
result = vkAllocateMemory(g_vkDevice, &allocInfo, nullptr, &tex.memory);
if (result != VK_SUCCESS || tex.memory == VK_NULL_HANDLE) {
debugVkResult("Failed to allocate texture image memory", result);
destroyTextureGpuResources(tex);
return false;
}
result = vkBindImageMemory(g_vkDevice, tex.image, tex.memory, 0);
if (result != VK_SUCCESS) {
debugVkResult("Failed to bind texture image memory", result);
destroyTextureGpuResources(tex);
return false;
}
VkImageViewCreateInfo viewCI = {};
viewCI.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewCI.image = tex.image;
viewCI.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewCI.format = VK_FORMAT_R8G8B8A8_UNORM;
viewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
viewCI.subresourceRange.baseMipLevel = 0;
viewCI.subresourceRange.levelCount = mipLevels;
viewCI.subresourceRange.baseArrayLayer = 0;
viewCI.subresourceRange.layerCount = 1;
result = vkCreateImageView(g_vkDevice, &viewCI, nullptr, &tex.imageView);
if (result != VK_SUCCESS || tex.imageView == VK_NULL_HANDLE) {
debugVkResult("Failed to create texture image view", result);
destroyTextureGpuResources(tex);
return false;
}
tex.width = width;
tex.height = height;
tex.mipLevels = mipLevels;
tex.imageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
if (!recreateTextureSampler(tex))
return false;
return true;
}
static bool uploadTextureRegionImmediate(VulkanTexture &tex, uint32_t level,
uint32_t xoffset, uint32_t yoffset,
uint32_t width, uint32_t height,
const void *data);
static bool ensureTextureUploadedFromCache(VulkanTexture &tex) {
if (!tex.pendingUpload)
return true;
if (!hasTextureUploadContext())
return false;
uint32_t baseWidth = 0;
uint32_t baseHeight = 0;
uint32_t mipLevels = 1;
if (!computeTextureDimensionsFromCache(tex, baseWidth, baseHeight, mipLevels))
return false;
const bool needsRecreate = (tex.image == VK_NULL_HANDLE) ||
(tex.width != baseWidth) ||
(tex.height != baseHeight) ||
(tex.mipLevels != mipLevels);
if (needsRecreate) {
if (!createTextureImageAndView(tex, baseWidth, baseHeight, mipLevels))
return false;
}
for (uint32_t level = 0; level < static_cast<uint32_t>(tex.levelData.size());
++level) {
const VulkanTextureLevelData &ld = tex.levelData[level];
if (!ld.valid || ld.pixels.empty())
continue;
if (level >= tex.mipLevels)
continue;
if (!uploadTextureRegionImmediate(tex, level, 0, 0, ld.width, ld.height,
ld.pixels.data())) {
return false;
}
}
tex.pendingUpload = false;
return true;
}
static void processPendingTextureUploads() {
if (!hasTextureUploadContext())
return;
for (auto &kv : g_vkTextures) {
VulkanTexture &tex = kv.second;
if (tex.pendingUpload) {
ensureTextureUploadedFromCache(tex);
}
}
}
static bool uploadTextureRegionImmediate(VulkanTexture &tex, uint32_t level,
uint32_t xoffset, uint32_t yoffset,
uint32_t width, uint32_t height,
const void *data) {
if (g_vkDevice == VK_NULL_HANDLE || g_vkGraphicsQueue == VK_NULL_HANDLE ||
g_vkCommandPool == VK_NULL_HANDLE || tex.image == VK_NULL_HANDLE ||
data == nullptr || width == 0 || height == 0 ||
level >= tex.mipLevels) {
return false;
}
const size_t uploadBytes =
static_cast<size_t>(width) * static_cast<size_t>(height) * 4u;
if (uploadBytes == 0)
return false;
VkBuffer stagingBuffer = VK_NULL_HANDLE;
VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
bool stagingHostCoherent = true;
VkBufferCreateInfo bufCI = {};
bufCI.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufCI.size = static_cast<VkDeviceSize>(uploadBytes);
bufCI.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
bufCI.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VkResult result = vkCreateBuffer(g_vkDevice, &bufCI, nullptr, &stagingBuffer);
if (result != VK_SUCCESS || stagingBuffer == VK_NULL_HANDLE) {
debugVkResult("Failed to create texture staging buffer", result);
return false;
}
VkMemoryRequirements memReq = {};
vkGetBufferMemoryRequirements(g_vkDevice, stagingBuffer, &memReq);
bool foundMemory = false;
VkMemoryPropertyFlags memFlags =
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
uint32_t memoryTypeIndex =
findMemoryTypeIndex(memReq.memoryTypeBits, memFlags, foundMemory);
if (!foundMemory) {
memFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
memoryTypeIndex =
findMemoryTypeIndex(memReq.memoryTypeBits, memFlags, foundMemory);
stagingHostCoherent = false;
}
if (!foundMemory) {
debugVk("C4JRender_Vulkan: No host-visible memory type for texture staging.\n");
vkDestroyBuffer(g_vkDevice, stagingBuffer, nullptr);
return false;
}
VkMemoryAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memReq.size;
allocInfo.memoryTypeIndex = memoryTypeIndex;
result = vkAllocateMemory(g_vkDevice, &allocInfo, nullptr, &stagingMemory);
if (result != VK_SUCCESS || stagingMemory == VK_NULL_HANDLE) {
debugVkResult("Failed to allocate texture staging memory", result);
vkDestroyBuffer(g_vkDevice, stagingBuffer, nullptr);
return false;
}
result = vkBindBufferMemory(g_vkDevice, stagingBuffer, stagingMemory, 0);
if (result != VK_SUCCESS) {
debugVkResult("Failed to bind texture staging memory", result);
vkFreeMemory(g_vkDevice, stagingMemory, nullptr);
vkDestroyBuffer(g_vkDevice, stagingBuffer, nullptr);
return false;
}
void *mapped = nullptr;
result = vkMapMemory(g_vkDevice, stagingMemory, 0, uploadBytes, 0, &mapped);
if (result != VK_SUCCESS || mapped == nullptr) {
debugVkResult("Failed to map texture staging memory", result);
vkFreeMemory(g_vkDevice, stagingMemory, nullptr);
vkDestroyBuffer(g_vkDevice, stagingBuffer, nullptr);
return false;
}
std::memcpy(mapped, data, uploadBytes);
if (!stagingHostCoherent) {
VkMappedMemoryRange range = {};
range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
range.memory = stagingMemory;
range.offset = 0;
range.size = VK_WHOLE_SIZE;
result = vkFlushMappedMemoryRanges(g_vkDevice, 1, &range);
if (result != VK_SUCCESS) {
debugVkResult("Failed to flush texture staging memory", result);
vkUnmapMemory(g_vkDevice, stagingMemory);
vkFreeMemory(g_vkDevice, stagingMemory, nullptr);
vkDestroyBuffer(g_vkDevice, stagingBuffer, nullptr);
return false;
}
}
vkUnmapMemory(g_vkDevice, stagingMemory);
VkCommandBuffer uploadCmd = VK_NULL_HANDLE;
VkCommandBufferAllocateInfo cbAI = {};
cbAI.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
cbAI.commandPool = g_vkCommandPool;
cbAI.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
cbAI.commandBufferCount = 1;
result = vkAllocateCommandBuffers(g_vkDevice, &cbAI, &uploadCmd);
if (result != VK_SUCCESS || uploadCmd == VK_NULL_HANDLE) {
debugVkResult("Failed to allocate texture upload command buffer", result);
vkFreeMemory(g_vkDevice, stagingMemory, nullptr);
vkDestroyBuffer(g_vkDevice, stagingBuffer, nullptr);
return false;
}
VkCommandBufferBeginInfo beginInfo = {};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
result = vkBeginCommandBuffer(uploadCmd, &beginInfo);
if (result != VK_SUCCESS) {
debugVkResult("Failed to begin texture upload command buffer", result);
vkFreeCommandBuffers(g_vkDevice, g_vkCommandPool, 1, &uploadCmd);
vkFreeMemory(g_vkDevice, stagingMemory, nullptr);
vkDestroyBuffer(g_vkDevice, stagingBuffer, nullptr);
return false;
}
VkImageMemoryBarrier toTransfer = {};
toTransfer.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
toTransfer.oldLayout = tex.imageLayout;
toTransfer.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
toTransfer.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
toTransfer.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
toTransfer.image = tex.image;
toTransfer.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
toTransfer.subresourceRange.baseMipLevel = level;
toTransfer.subresourceRange.levelCount = 1;
toTransfer.subresourceRange.baseArrayLayer = 0;
toTransfer.subresourceRange.layerCount = 1;
VkPipelineStageFlags srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
if (tex.imageLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
srcStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
toTransfer.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
} else if (tex.imageLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
toTransfer.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
} else {
toTransfer.srcAccessMask = 0;
}
toTransfer.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
vkCmdPipelineBarrier(uploadCmd, srcStage, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0,
nullptr, 0, nullptr, 1, &toTransfer);
VkBufferImageCopy copyRegion = {};
copyRegion.bufferOffset = 0;
copyRegion.bufferRowLength = 0;
copyRegion.bufferImageHeight = 0;
copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copyRegion.imageSubresource.mipLevel = level;
copyRegion.imageSubresource.baseArrayLayer = 0;
copyRegion.imageSubresource.layerCount = 1;
copyRegion.imageOffset = {static_cast<int32_t>(xoffset),
static_cast<int32_t>(yoffset), 0};
copyRegion.imageExtent = {width, height, 1};
vkCmdCopyBufferToImage(uploadCmd, stagingBuffer, tex.image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copyRegion);
VkImageMemoryBarrier toRead = {};
toRead.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
toRead.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
toRead.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
toRead.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
toRead.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
toRead.image = tex.image;
toRead.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
toRead.subresourceRange.baseMipLevel = level;
toRead.subresourceRange.levelCount = 1;
toRead.subresourceRange.baseArrayLayer = 0;
toRead.subresourceRange.layerCount = 1;
toRead.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
toRead.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
vkCmdPipelineBarrier(uploadCmd, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0,
nullptr, 1, &toRead);
result = vkEndCommandBuffer(uploadCmd);
if (result != VK_SUCCESS) {
debugVkResult("Failed to end texture upload command buffer", result);
vkFreeCommandBuffers(g_vkDevice, g_vkCommandPool, 1, &uploadCmd);
vkFreeMemory(g_vkDevice, stagingMemory, nullptr);
vkDestroyBuffer(g_vkDevice, stagingBuffer, nullptr);
return false;
}
VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &uploadCmd;
result = vkQueueSubmit(g_vkGraphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
if (result != VK_SUCCESS) {
debugVkResult("Failed to submit texture upload", result);
vkFreeCommandBuffers(g_vkDevice, g_vkCommandPool, 1, &uploadCmd);
vkFreeMemory(g_vkDevice, stagingMemory, nullptr);
vkDestroyBuffer(g_vkDevice, stagingBuffer, nullptr);
return false;
}
result = vkQueueWaitIdle(g_vkGraphicsQueue);
if (result != VK_SUCCESS) {
debugVkResult("Failed waiting for texture upload queue idle", result);
vkFreeCommandBuffers(g_vkDevice, g_vkCommandPool, 1, &uploadCmd);
vkFreeMemory(g_vkDevice, stagingMemory, nullptr);
vkDestroyBuffer(g_vkDevice, stagingBuffer, nullptr);
return false;
}
vkFreeCommandBuffers(g_vkDevice, g_vkCommandPool, 1, &uploadCmd);
vkFreeMemory(g_vkDevice, stagingMemory, nullptr);
vkDestroyBuffer(g_vkDevice, stagingBuffer, nullptr);
tex.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
updateTextureDescriptorSet(tex);
return true;
}
static VkDescriptorSet resolveTextureDescriptorSet(int textureId) {
if (textureId >= 0) {
auto it = g_vkTextures.find(textureId);
if (it != g_vkTextures.end()) {
VulkanTexture &tex = it->second;
if (tex.pendingUpload) {
ensureTextureUploadedFromCache(tex);
} else if (tex.descriptorSet == VK_NULL_HANDLE &&
tex.image != VK_NULL_HANDLE && hasTextureUploadContext()) {
updateTextureDescriptorSet(tex);
}
if (tex.descriptorSet != VK_NULL_HANDLE) {
return tex.descriptorSet;
}
}
}
if (g_vkWhiteTextureReady &&
g_vkWhiteTexture.descriptorSet != VK_NULL_HANDLE) {
return g_vkWhiteTexture.descriptorSet;
}
return VK_NULL_HANDLE;
}
static bool ensureWhiteTexture() {
if (g_vkWhiteTextureReady &&
g_vkWhiteTexture.descriptorSet != VK_NULL_HANDLE) {
return true;
}
g_vkWhiteTexture.id = 0;
g_vkWhiteTexture.width = 0;
g_vkWhiteTexture.height = 0;
g_vkWhiteTexture.mipLevels = 1;
g_vkWhiteTexture.image = VK_NULL_HANDLE;
g_vkWhiteTexture.memory = VK_NULL_HANDLE;
g_vkWhiteTexture.imageView = VK_NULL_HANDLE;
g_vkWhiteTexture.sampler = VK_NULL_HANDLE;
g_vkWhiteTexture.descriptorSet = VK_NULL_HANDLE;
g_vkWhiteTexture.imageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
g_vkWhiteTexture.minFilter = GL_NEAREST;
g_vkWhiteTexture.magFilter = GL_NEAREST;
g_vkWhiteTexture.wrapS = GL_REPEAT;
g_vkWhiteTexture.wrapT = GL_REPEAT;
g_vkWhiteTexture.requestedMipLevels = 1;
g_vkWhiteTexture.pendingUpload = false;
g_vkWhiteTexture.levelData.clear();
if (!createTextureImageAndView(g_vkWhiteTexture, 1, 1, 1))
return false;
const uint8_t white[4] = {255, 255, 255, 255};
if (!uploadTextureRegionImmediate(g_vkWhiteTexture, 0, 0, 0, 1, 1, white))
return false;
g_vkWhiteTextureReady = true;
return true;
}
static void destroyAllTextures(bool preserveCpuCache) {
for (auto it = g_vkTextures.begin(); it != g_vkTextures.end();) {
destroyTextureGpuResources(it->second);
if (preserveCpuCache) {
bool hasCachedData = false;
for (const auto &level : it->second.levelData) {
if (level.valid && !level.pixels.empty()) {
hasCachedData = true;
break;
}
}
it->second.pendingUpload = hasCachedData;
++it;
} else {
it = g_vkTextures.erase(it);
}
}
destroyTextureGpuResources(g_vkWhiteTexture);
g_vkWhiteTexture = {};
g_vkWhiteTextureReady = false;
if (!preserveCpuCache) {
g_vkPendingTextureLevels = 1;
}
}
static void destroyDepthResources() {
if (g_vkDevice == VK_NULL_HANDLE)
return;
if (g_vkDepthImageView != VK_NULL_HANDLE) {
vkDestroyImageView(g_vkDevice, g_vkDepthImageView, nullptr);
g_vkDepthImageView = VK_NULL_HANDLE;
}
if (g_vkDepthImage != VK_NULL_HANDLE) {
vkDestroyImage(g_vkDevice, g_vkDepthImage, nullptr);
g_vkDepthImage = VK_NULL_HANDLE;
}
if (g_vkDepthMemory != VK_NULL_HANDLE) {
vkFreeMemory(g_vkDevice, g_vkDepthMemory, nullptr);
g_vkDepthMemory = VK_NULL_HANDLE;
}
g_vkDepthFormat = VK_FORMAT_UNDEFINED;
}
static bool createDepthResources(uint32_t width, uint32_t height) {
if (g_vkDevice == VK_NULL_HANDLE || width == 0 || height == 0)
return false;
destroyDepthResources();
g_vkDepthFormat = chooseDepthFormat();
if (g_vkDepthFormat == VK_FORMAT_UNDEFINED) {
debugVk("C4JRender_Vulkan: No supported depth format.\n");
return false;
}
VkImageCreateInfo imageCI = {};
imageCI.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageCI.imageType = VK_IMAGE_TYPE_2D;
imageCI.format = g_vkDepthFormat;
imageCI.extent = {width, height, 1};
imageCI.mipLevels = 1;
imageCI.arrayLayers = 1;
imageCI.samples = VK_SAMPLE_COUNT_1_BIT;
imageCI.tiling = VK_IMAGE_TILING_OPTIMAL;
imageCI.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
imageCI.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageCI.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VkResult result = vkCreateImage(g_vkDevice, &imageCI, nullptr, &g_vkDepthImage);
if (result != VK_SUCCESS || g_vkDepthImage == VK_NULL_HANDLE) {
debugVkResult("Failed to create depth image", result);
destroyDepthResources();
return false;
}
VkMemoryRequirements memReq = {};
vkGetImageMemoryRequirements(g_vkDevice, g_vkDepthImage, &memReq);
bool found = false;
uint32_t memoryTypeIndex = findMemoryTypeIndex(
memReq.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, found);
if (!found) {
debugVk("C4JRender_Vulkan: No device-local memory type for depth image.\n");
destroyDepthResources();
return false;
}
VkMemoryAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memReq.size;
allocInfo.memoryTypeIndex = memoryTypeIndex;
result = vkAllocateMemory(g_vkDevice, &allocInfo, nullptr, &g_vkDepthMemory);
if (result != VK_SUCCESS || g_vkDepthMemory == VK_NULL_HANDLE) {
debugVkResult("Failed to allocate depth image memory", result);
destroyDepthResources();
return false;
}
result = vkBindImageMemory(g_vkDevice, g_vkDepthImage, g_vkDepthMemory, 0);
if (result != VK_SUCCESS) {
debugVkResult("Failed to bind depth image memory", result);
destroyDepthResources();
return false;
}
VkImageViewCreateInfo viewCI = {};
viewCI.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewCI.image = g_vkDepthImage;
viewCI.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewCI.format = g_vkDepthFormat;
viewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
if (hasStencilComponent(g_vkDepthFormat))
viewCI.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
viewCI.subresourceRange.baseMipLevel = 0;
viewCI.subresourceRange.levelCount = 1;
viewCI.subresourceRange.baseArrayLayer = 0;
viewCI.subresourceRange.layerCount = 1;
result = vkCreateImageView(g_vkDevice, &viewCI, nullptr, &g_vkDepthImageView);
if (result != VK_SUCCESS || g_vkDepthImageView == VK_NULL_HANDLE) {
debugVkResult("Failed to create depth image view", result);
destroyDepthResources();
return false;
}
return true;
}
static void destroyDynamicVertexBuffer() {
if (g_vkDevice == VK_NULL_HANDLE)
return;
if (g_vkDynamicVertexMemory != VK_NULL_HANDLE && g_vkDynamicVertexMapped != nullptr) {
vkUnmapMemory(g_vkDevice, g_vkDynamicVertexMemory);
g_vkDynamicVertexMapped = nullptr;
}
if (g_vkDynamicVertexBuffer != VK_NULL_HANDLE) {
vkDestroyBuffer(g_vkDevice, g_vkDynamicVertexBuffer, nullptr);
g_vkDynamicVertexBuffer = VK_NULL_HANDLE;
}
if (g_vkDynamicVertexMemory != VK_NULL_HANDLE) {
vkFreeMemory(g_vkDevice, g_vkDynamicVertexMemory, nullptr);
g_vkDynamicVertexMemory = VK_NULL_HANDLE;
}
g_vkDynamicVertexCapacity = 0;
g_vkDynamicVertexHostCoherent = true;
}
static bool ensureDynamicVertexBuffer(size_t minBytes) {
if (minBytes == 0)
return true;
if (g_vkDevice == VK_NULL_HANDLE)
return false;
if (g_vkDynamicVertexBuffer != VK_NULL_HANDLE &&
g_vkDynamicVertexCapacity >= minBytes) {
return true;
}
size_t newCapacity = 1u << 20; // 1MB default.
if (newCapacity < minBytes)
newCapacity = minBytes;
if (g_vkDynamicVertexCapacity > 0 &&
newCapacity < g_vkDynamicVertexCapacity * 2) {
newCapacity = g_vkDynamicVertexCapacity * 2;
if (newCapacity < minBytes)
newCapacity = minBytes;
}
destroyDynamicVertexBuffer();
VkBufferCreateInfo bufferCI = {};
bufferCI.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferCI.size = static_cast<VkDeviceSize>(newCapacity);
bufferCI.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
bufferCI.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VkResult result =
vkCreateBuffer(g_vkDevice, &bufferCI, nullptr, &g_vkDynamicVertexBuffer);
if (result != VK_SUCCESS || g_vkDynamicVertexBuffer == VK_NULL_HANDLE) {
debugVkResult("Failed to create dynamic vertex buffer", result);
return false;
}
VkMemoryRequirements memReq = {};
vkGetBufferMemoryRequirements(g_vkDevice, g_vkDynamicVertexBuffer, &memReq);
bool found = false;
uint32_t memoryTypeIndex =
findMemoryTypeIndex(memReq.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
found);
g_vkDynamicVertexHostCoherent = true;
if (!found) {
memoryTypeIndex = findMemoryTypeIndex(
memReq.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, found);
g_vkDynamicVertexHostCoherent = false;
}
if (!found) {
debugVk("C4JRender_Vulkan: No host-visible memory type for vertex buffer.\n");
destroyDynamicVertexBuffer();
return false;
}
VkMemoryAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memReq.size;
allocInfo.memoryTypeIndex = memoryTypeIndex;
result = vkAllocateMemory(g_vkDevice, &allocInfo, nullptr, &g_vkDynamicVertexMemory);
if (result != VK_SUCCESS || g_vkDynamicVertexMemory == VK_NULL_HANDLE) {
debugVkResult("Failed to allocate dynamic vertex buffer memory", result);
destroyDynamicVertexBuffer();
return false;
}
result = vkBindBufferMemory(g_vkDevice, g_vkDynamicVertexBuffer,
g_vkDynamicVertexMemory, 0);
if (result != VK_SUCCESS) {
debugVkResult("Failed to bind dynamic vertex buffer memory", result);
destroyDynamicVertexBuffer();
return false;
}
g_vkDynamicVertexCapacity = newCapacity;
void *mapped = nullptr;
result = vkMapMemory(g_vkDevice, g_vkDynamicVertexMemory, 0, VK_WHOLE_SIZE, 0,
&mapped);
if (result != VK_SUCCESS || mapped == nullptr) {
debugVkResult("Failed to map dynamic vertex buffer memory", result);
destroyDynamicVertexBuffer();
return false;
}
g_vkDynamicVertexMapped = static_cast<uint8_t *>(mapped);
return true;
}
static void destroyUiStagingBuffer() {
if (g_vkDevice == VK_NULL_HANDLE)
return;
if (g_vkUiStagingMemory != VK_NULL_HANDLE && g_vkUiStagingMapped != nullptr) {
vkUnmapMemory(g_vkDevice, g_vkUiStagingMemory);
g_vkUiStagingMapped = nullptr;
}
if (g_vkUiStagingBuffer != VK_NULL_HANDLE) {
vkDestroyBuffer(g_vkDevice, g_vkUiStagingBuffer, nullptr);
g_vkUiStagingBuffer = VK_NULL_HANDLE;
}
if (g_vkUiStagingMemory != VK_NULL_HANDLE) {
vkFreeMemory(g_vkDevice, g_vkUiStagingMemory, nullptr);
g_vkUiStagingMemory = VK_NULL_HANDLE;
}
g_vkUiStagingCapacity = 0;
g_vkUiStagingHostCoherent = true;
}
static bool ensureUiStagingBuffer(size_t minBytes) {
if (minBytes == 0)
return true;
if (g_vkDevice == VK_NULL_HANDLE)
return false;
if (g_vkUiStagingBuffer != VK_NULL_HANDLE && g_vkUiStagingCapacity >= minBytes)
return true;
size_t newCapacity = 1u << 20; // 1MB default.
if (newCapacity < minBytes)
newCapacity = minBytes;
destroyUiStagingBuffer();
VkBufferCreateInfo bufferCI = {};
bufferCI.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferCI.size = static_cast<VkDeviceSize>(newCapacity);
bufferCI.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
bufferCI.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VkResult result =
vkCreateBuffer(g_vkDevice, &bufferCI, nullptr, &g_vkUiStagingBuffer);
if (result != VK_SUCCESS || g_vkUiStagingBuffer == VK_NULL_HANDLE) {
debugVkResult("Failed to create UI staging buffer", result);
return false;
}
VkMemoryRequirements memReq = {};
vkGetBufferMemoryRequirements(g_vkDevice, g_vkUiStagingBuffer, &memReq);
bool found = false;
uint32_t memoryTypeIndex =
findMemoryTypeIndex(memReq.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
found);
g_vkUiStagingHostCoherent = true;
if (!found) {
memoryTypeIndex = findMemoryTypeIndex(
memReq.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, found);
g_vkUiStagingHostCoherent = false;
}
if (!found) {
debugVk("C4JRender_Vulkan: No host-visible memory type for UI staging buffer.\n");
destroyUiStagingBuffer();
return false;
}
VkMemoryAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memReq.size;
allocInfo.memoryTypeIndex = memoryTypeIndex;
result = vkAllocateMemory(g_vkDevice, &allocInfo, nullptr, &g_vkUiStagingMemory);
if (result != VK_SUCCESS || g_vkUiStagingMemory == VK_NULL_HANDLE) {
debugVkResult("Failed to allocate UI staging buffer memory", result);
destroyUiStagingBuffer();
return false;
}
result =
vkBindBufferMemory(g_vkDevice, g_vkUiStagingBuffer, g_vkUiStagingMemory, 0);
if (result != VK_SUCCESS) {
debugVkResult("Failed to bind UI staging buffer memory", result);
destroyUiStagingBuffer();
return false;
}
g_vkUiStagingCapacity = newCapacity;
void *mapped = nullptr;
result = vkMapMemory(g_vkDevice, g_vkUiStagingMemory, 0, VK_WHOLE_SIZE, 0,
&mapped);
if (result != VK_SUCCESS || mapped == nullptr) {
debugVkResult("Failed to map UI staging buffer memory", result);
destroyUiStagingBuffer();
return false;
}
g_vkUiStagingMapped = static_cast<uint8_t *>(mapped);
return true;
}
static void destroyUiImageResources() {
if (g_vkDevice == VK_NULL_HANDLE)
return;
if (g_vkUiSampler != VK_NULL_HANDLE) {
vkDestroySampler(g_vkDevice, g_vkUiSampler, nullptr);
g_vkUiSampler = VK_NULL_HANDLE;
}
if (g_vkUiImageView != VK_NULL_HANDLE) {
vkDestroyImageView(g_vkDevice, g_vkUiImageView, nullptr);
g_vkUiImageView = VK_NULL_HANDLE;
}
if (g_vkUiImage != VK_NULL_HANDLE) {
vkDestroyImage(g_vkDevice, g_vkUiImage, nullptr);
g_vkUiImage = VK_NULL_HANDLE;
}
if (g_vkUiImageMemory != VK_NULL_HANDLE) {
vkFreeMemory(g_vkDevice, g_vkUiImageMemory, nullptr);
g_vkUiImageMemory = VK_NULL_HANDLE;
}
g_vkUiImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
g_vkUiImageWidth = 0;
g_vkUiImageHeight = 0;
g_vkUiImageReady = false;
}
static bool createOrResizeUiImageResources(uint32_t width, uint32_t height) {
if (width == 0 || height == 0 || g_vkDevice == VK_NULL_HANDLE)
return false;
if (g_vkUiImage != VK_NULL_HANDLE && g_vkUiImageWidth == width &&
g_vkUiImageHeight == height && g_vkUiImageView != VK_NULL_HANDLE &&
g_vkUiSampler != VK_NULL_HANDLE) {
return true;
}
destroyUiImageResources();
VkImageCreateInfo imageCI = {};
imageCI.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageCI.imageType = VK_IMAGE_TYPE_2D;
imageCI.format = VK_FORMAT_B8G8R8A8_UNORM;
imageCI.extent.width = width;
imageCI.extent.height = height;
imageCI.extent.depth = 1;
imageCI.mipLevels = 1;
imageCI.arrayLayers = 1;
imageCI.samples = VK_SAMPLE_COUNT_1_BIT;
imageCI.tiling = VK_IMAGE_TILING_OPTIMAL;
imageCI.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
imageCI.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageCI.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VkResult result = vkCreateImage(g_vkDevice, &imageCI, nullptr, &g_vkUiImage);
if (result != VK_SUCCESS || g_vkUiImage == VK_NULL_HANDLE) {
debugVkResult("Failed to create UI image", result);
return false;
}
VkMemoryRequirements memReq = {};
vkGetImageMemoryRequirements(g_vkDevice, g_vkUiImage, &memReq);
bool found = false;
uint32_t memoryTypeIndex =
findMemoryTypeIndex(memReq.memoryTypeBits,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, found);
if (!found) {
debugVk("C4JRender_Vulkan: No device-local memory type for UI image.\n");
destroyUiImageResources();
return false;
}
VkMemoryAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memReq.size;
allocInfo.memoryTypeIndex = memoryTypeIndex;
result = vkAllocateMemory(g_vkDevice, &allocInfo, nullptr, &g_vkUiImageMemory);
if (result != VK_SUCCESS || g_vkUiImageMemory == VK_NULL_HANDLE) {
debugVkResult("Failed to allocate UI image memory", result);
destroyUiImageResources();
return false;
}
result = vkBindImageMemory(g_vkDevice, g_vkUiImage, g_vkUiImageMemory, 0);
if (result != VK_SUCCESS) {
debugVkResult("Failed to bind UI image memory", result);
destroyUiImageResources();
return false;
}
VkImageViewCreateInfo viewCI = {};
viewCI.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewCI.image = g_vkUiImage;
viewCI.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewCI.format = VK_FORMAT_B8G8R8A8_UNORM;
viewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
viewCI.subresourceRange.baseMipLevel = 0;
viewCI.subresourceRange.levelCount = 1;
viewCI.subresourceRange.baseArrayLayer = 0;
viewCI.subresourceRange.layerCount = 1;
result = vkCreateImageView(g_vkDevice, &viewCI, nullptr, &g_vkUiImageView);
if (result != VK_SUCCESS || g_vkUiImageView == VK_NULL_HANDLE) {
debugVkResult("Failed to create UI image view", result);
destroyUiImageResources();
return false;
}
VkSamplerCreateInfo samplerCI = {};
samplerCI.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
samplerCI.magFilter = VK_FILTER_LINEAR;
samplerCI.minFilter = VK_FILTER_LINEAR;
samplerCI.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
samplerCI.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerCI.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerCI.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerCI.minLod = 0.0f;
samplerCI.maxLod = 0.0f;
samplerCI.maxAnisotropy = 1.0f;
result = vkCreateSampler(g_vkDevice, &samplerCI, nullptr, &g_vkUiSampler);
if (result != VK_SUCCESS || g_vkUiSampler == VK_NULL_HANDLE) {
debugVkResult("Failed to create UI sampler", result);
destroyUiImageResources();
return false;
}
g_vkUiImageWidth = width;
g_vkUiImageHeight = height;
g_vkUiImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
g_vkUiImageReady = false;
return true;
}
static void destroySwapchainDrawResources() {
if (g_vkDevice != VK_NULL_HANDLE) {
for (VkFramebuffer fb : g_vkFramebuffers) {
if (fb != VK_NULL_HANDLE)
vkDestroyFramebuffer(g_vkDevice, fb, nullptr);
}
g_vkFramebuffers.clear();
for (auto &kv : g_vkTrianglePipelines) {
if (kv.second != VK_NULL_HANDLE)
vkDestroyPipeline(g_vkDevice, kv.second, nullptr);
}
g_vkTrianglePipelines.clear();
g_vkTrianglePipeline = VK_NULL_HANDLE;
if (g_vkUiPipeline != VK_NULL_HANDLE) {
vkDestroyPipeline(g_vkDevice, g_vkUiPipeline, nullptr);
g_vkUiPipeline = VK_NULL_HANDLE;
}
if (g_vkPipelineLayout != VK_NULL_HANDLE) {
vkDestroyPipelineLayout(g_vkDevice, g_vkPipelineLayout, nullptr);
g_vkPipelineLayout = VK_NULL_HANDLE;
}
if (g_vkTriangleDescriptorPool != VK_NULL_HANDLE) {
vkDestroyDescriptorPool(g_vkDevice, g_vkTriangleDescriptorPool, nullptr);
g_vkTriangleDescriptorPool = VK_NULL_HANDLE;
}
for (auto &kv : g_vkTextures) {
kv.second.descriptorSet = VK_NULL_HANDLE;
}
g_vkWhiteTexture.descriptorSet = VK_NULL_HANDLE;
if (g_vkTriangleDescriptorSetLayout != VK_NULL_HANDLE) {
vkDestroyDescriptorSetLayout(g_vkDevice, g_vkTriangleDescriptorSetLayout,
nullptr);
g_vkTriangleDescriptorSetLayout = VK_NULL_HANDLE;
}
g_vkWhiteTextureReady = false;
if (g_vkUiPipelineLayout != VK_NULL_HANDLE) {
vkDestroyPipelineLayout(g_vkDevice, g_vkUiPipelineLayout, nullptr);
g_vkUiPipelineLayout = VK_NULL_HANDLE;
}
if (g_vkUiDescriptorPool != VK_NULL_HANDLE) {
vkDestroyDescriptorPool(g_vkDevice, g_vkUiDescriptorPool, nullptr);
g_vkUiDescriptorPool = VK_NULL_HANDLE;
}
if (g_vkUiDescriptorSetLayout != VK_NULL_HANDLE) {
vkDestroyDescriptorSetLayout(g_vkDevice, g_vkUiDescriptorSetLayout,
nullptr);
g_vkUiDescriptorSetLayout = VK_NULL_HANDLE;
}
g_vkUiDescriptorSet = VK_NULL_HANDLE;
if (g_vkRenderPass != VK_NULL_HANDLE) {
vkDestroyRenderPass(g_vkDevice, g_vkRenderPass, nullptr);
g_vkRenderPass = VK_NULL_HANDLE;
}
destroyDepthResources();
for (VkImageView view : g_vkSwapImageViews) {
if (view != VK_NULL_HANDLE)
vkDestroyImageView(g_vkDevice, view, nullptr);
}
}
g_vkSwapImageViews.clear();
}
static void destroyVulkanRuntime() {
if (g_vkDevice != VK_NULL_HANDLE)
vkDeviceWaitIdle(g_vkDevice);
destroyAllTextures(true);
destroySwapchainDrawResources();
destroyDynamicVertexBuffer();
destroyUiStagingBuffer();
destroyUiImageResources();
if (g_vkDevice != VK_NULL_HANDLE) {
if (g_vkImageAvailable != VK_NULL_HANDLE) {
vkDestroySemaphore(g_vkDevice, g_vkImageAvailable, nullptr);
g_vkImageAvailable = VK_NULL_HANDLE;
}
if (g_vkRenderFinished != VK_NULL_HANDLE) {
vkDestroySemaphore(g_vkDevice, g_vkRenderFinished, nullptr);
g_vkRenderFinished = VK_NULL_HANDLE;
}
if (g_vkFence != VK_NULL_HANDLE) {
vkDestroyFence(g_vkDevice, g_vkFence, nullptr);
g_vkFence = VK_NULL_HANDLE;
}
if (g_vkCommandPool != VK_NULL_HANDLE) {
vkDestroyCommandPool(g_vkDevice, g_vkCommandPool, nullptr);
g_vkCommandPool = VK_NULL_HANDLE;
g_vkCommandBuffer = VK_NULL_HANDLE;
}
if (g_vkSwapchain != VK_NULL_HANDLE) {
vkDestroySwapchainKHR(g_vkDevice, g_vkSwapchain, nullptr);
g_vkSwapchain = VK_NULL_HANDLE;
}
vkDestroyDevice(g_vkDevice, nullptr);
g_vkDevice = VK_NULL_HANDLE;
g_vkGraphicsQueue = VK_NULL_HANDLE;
}
if (g_vkSurface != VK_NULL_HANDLE) {
vkDestroySurfaceKHR(g_vkInstance, g_vkSurface, nullptr);
g_vkSurface = VK_NULL_HANDLE;
}
if (g_vkInstance != VK_NULL_HANDLE) {
vkDestroyInstance(g_vkInstance, nullptr);
g_vkInstance = VK_NULL_HANDLE;
}
g_vkPhysicalDevice = VK_NULL_HANDLE;
g_vkGraphicsFamily = 0;
g_vkSwapImages.clear();
g_vkSwapImageLayouts.clear();
g_vkFrameVertexData.clear();
g_vkQueuedDraws.clear();
{
std::lock_guard<std::mutex> commandListsLock(g_vkCommandListsMutex);
g_vkCommandLists.clear();
}
g_vkIsRecordingCommandList = false;
g_vkRecordingCommandListIndex = -1;
g_vkSwapExtent = {0, 0};
g_vkUiUpload.pixelsBGRA.clear();
g_vkUiUpload.width = 0;
g_vkUiUpload.height = 0;
g_vkUiUpload.pending = false;
resetThreadLocalRenderState();
g_vkRecordingHasStateChanges = false;
g_vkRecordingHasTextureStateChanges = false;
g_vkRecordingHasAlphaStateChanges = false;
g_vkInitialized = false;
}
static VkShaderModule createShaderModule(const uint32_t *code,
size_t codeWordCount) {
if (g_vkDevice == VK_NULL_HANDLE || !code || codeWordCount == 0)
return VK_NULL_HANDLE;
VkShaderModuleCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
createInfo.codeSize = codeWordCount * sizeof(uint32_t);
createInfo.pCode = code;
VkShaderModule module = VK_NULL_HANDLE;
VkResult result = vkCreateShaderModule(g_vkDevice, &createInfo, nullptr, &module);
if (result != VK_SUCCESS) {
debugVkResult("Failed to create shader module", result);
return VK_NULL_HANDLE;
}
return module;
}
static VkPipeline createTrianglePipeline(bool depthTestEnable,
bool depthWriteEnable,
VkCompareOp depthCompareOp,
bool blendEnable,
VkBlendFactor srcBlendFactor,
VkBlendFactor dstBlendFactor,
VkColorComponentFlags colorWriteMask,
bool cullEnable,
bool cullClockwise) {
if (g_vkDevice == VK_NULL_HANDLE || g_vkRenderPass == VK_NULL_HANDLE ||
g_vkPipelineLayout == VK_NULL_HANDLE)
return VK_NULL_HANDLE;
VkShaderModule vertModule =
createShaderModule(kTriangleVertSpv,
sizeof(kTriangleVertSpv) / sizeof(kTriangleVertSpv[0]));
VkShaderModule fragModule =
createShaderModule(kTriangleFragSpv,
sizeof(kTriangleFragSpv) / sizeof(kTriangleFragSpv[0]));
if (vertModule == VK_NULL_HANDLE || fragModule == VK_NULL_HANDLE) {
if (vertModule != VK_NULL_HANDLE)
vkDestroyShaderModule(g_vkDevice, vertModule, nullptr);
if (fragModule != VK_NULL_HANDLE)
vkDestroyShaderModule(g_vkDevice, fragModule, nullptr);
return VK_NULL_HANDLE;
}
VkPipelineShaderStageCreateInfo stages[2] = {};
stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
stages[0].module = vertModule;
stages[0].pName = "main";
stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
stages[1].module = fragModule;
stages[1].pName = "main";
VkVertexInputBindingDescription bindingDesc = {};
bindingDesc.binding = 0;
bindingDesc.stride = kVertexStridePF3TF2CB4NB4XW1;
bindingDesc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
VkVertexInputAttributeDescription attribs[3] = {};
attribs[0].location = 0;
attribs[0].binding = 0;
attribs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
attribs[0].offset = 0;
attribs[1].location = 1;
attribs[1].binding = 0;
attribs[1].format = VK_FORMAT_R32G32_SFLOAT;
attribs[1].offset = 12;
attribs[2].location = 2;
attribs[2].binding = 0;
attribs[2].format = VK_FORMAT_R8G8B8A8_UNORM;
attribs[2].offset = 20;
VkPipelineVertexInputStateCreateInfo viState = {};
viState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
viState.vertexBindingDescriptionCount = 1;
viState.pVertexBindingDescriptions = &bindingDesc;
viState.vertexAttributeDescriptionCount = 3;
viState.pVertexAttributeDescriptions = attribs;
VkPipelineInputAssemblyStateCreateInfo iaState = {};
iaState.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
iaState.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
iaState.primitiveRestartEnable = VK_FALSE;
VkViewport viewport = {};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = static_cast<float>(g_vkSwapExtent.width);
viewport.height = static_cast<float>(g_vkSwapExtent.height);
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
VkRect2D scissor = {};
scissor.offset = {0, 0};
scissor.extent = g_vkSwapExtent;
VkPipelineViewportStateCreateInfo vpState = {};
vpState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
vpState.viewportCount = 1;
vpState.pViewports = &viewport;
vpState.scissorCount = 1;
vpState.pScissors = &scissor;
VkPipelineRasterizationStateCreateInfo rsState = {};
rsState.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rsState.depthClampEnable = VK_FALSE;
rsState.rasterizerDiscardEnable = VK_FALSE;
rsState.polygonMode = VK_POLYGON_MODE_FILL;
rsState.cullMode = cullEnable ? VK_CULL_MODE_BACK_BIT : VK_CULL_MODE_NONE;
rsState.frontFace =
cullClockwise ? VK_FRONT_FACE_COUNTER_CLOCKWISE : VK_FRONT_FACE_CLOCKWISE;
rsState.depthBiasEnable = VK_FALSE;
rsState.lineWidth = 1.0f;
VkPipelineMultisampleStateCreateInfo msState = {};
msState.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
msState.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
VkPipelineColorBlendAttachmentState blendAttachment = {};
blendAttachment.blendEnable = blendEnable ? VK_TRUE : VK_FALSE;
blendAttachment.srcColorBlendFactor = srcBlendFactor;
blendAttachment.dstColorBlendFactor = dstBlendFactor;
blendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
blendAttachment.srcAlphaBlendFactor = srcBlendFactor;
blendAttachment.dstAlphaBlendFactor = dstBlendFactor;
blendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
blendAttachment.colorWriteMask = colorWriteMask;
VkPipelineColorBlendStateCreateInfo cbState = {};
cbState.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
cbState.attachmentCount = 1;
cbState.pAttachments = &blendAttachment;
VkPipelineDepthStencilStateCreateInfo depthState = {};
depthState.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depthState.depthTestEnable = depthTestEnable ? VK_TRUE : VK_FALSE;
depthState.depthWriteEnable = depthWriteEnable ? VK_TRUE : VK_FALSE;
depthState.depthCompareOp = depthCompareOp;
depthState.depthBoundsTestEnable = VK_FALSE;
depthState.stencilTestEnable = VK_FALSE;
VkGraphicsPipelineCreateInfo gpCI = {};
gpCI.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
gpCI.stageCount = 2;
gpCI.pStages = stages;
gpCI.pVertexInputState = &viState;
gpCI.pInputAssemblyState = &iaState;
gpCI.pViewportState = &vpState;
gpCI.pRasterizationState = &rsState;
gpCI.pMultisampleState = &msState;
gpCI.pDepthStencilState = &depthState;
gpCI.pColorBlendState = &cbState;
gpCI.layout = g_vkPipelineLayout;
gpCI.renderPass = g_vkRenderPass;
gpCI.subpass = 0;
VkPipeline pipeline = VK_NULL_HANDLE;
VkResult result =
vkCreateGraphicsPipelines(g_vkDevice, VK_NULL_HANDLE, 1, &gpCI, nullptr,
&pipeline);
vkDestroyShaderModule(g_vkDevice, vertModule, nullptr);
vkDestroyShaderModule(g_vkDevice, fragModule, nullptr);
if (result != VK_SUCCESS || pipeline == VK_NULL_HANDLE) {
debugVkResult("Failed to create triangle graphics pipeline", result);
return VK_NULL_HANDLE;
}
return pipeline;
}
static VkPipeline getOrCreateTrianglePipeline(bool depthTestEnable,
bool depthWriteEnable,
VkCompareOp depthCompareOp,
bool blendEnable,
VkBlendFactor srcBlendFactor,
VkBlendFactor dstBlendFactor,
VkColorComponentFlags colorWriteMask,
bool cullEnable,
bool cullClockwise) {
const uint64_t key =
makeTrianglePipelineKey(depthTestEnable, depthWriteEnable, depthCompareOp,
blendEnable, srcBlendFactor, dstBlendFactor,
colorWriteMask, cullEnable, cullClockwise);
auto it = g_vkTrianglePipelines.find(key);
if (it != g_vkTrianglePipelines.end())
return it->second;
VkPipeline pipeline = createTrianglePipeline(
depthTestEnable, depthWriteEnable, depthCompareOp, blendEnable,
srcBlendFactor, dstBlendFactor, colorWriteMask, cullEnable,
cullClockwise);
if (pipeline == VK_NULL_HANDLE)
return VK_NULL_HANDLE;
g_vkTrianglePipelines[key] = pipeline;
return pipeline;
}
static bool createTriangleRenderResources() {
if (g_vkDevice == VK_NULL_HANDLE || g_vkSwapImages.empty())
return false;
if (g_vkSwapExtent.width == 0 || g_vkSwapExtent.height == 0)
return false;
destroySwapchainDrawResources();
g_vkSwapImageViews.assign(g_vkSwapImages.size(), VK_NULL_HANDLE);
for (size_t i = 0; i < g_vkSwapImages.size(); ++i) {
VkImageViewCreateInfo ivCI = {};
ivCI.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
ivCI.image = g_vkSwapImages[i];
ivCI.viewType = VK_IMAGE_VIEW_TYPE_2D;
ivCI.format = g_vkSwapFormat;
ivCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
ivCI.subresourceRange.baseMipLevel = 0;
ivCI.subresourceRange.levelCount = 1;
ivCI.subresourceRange.baseArrayLayer = 0;
ivCI.subresourceRange.layerCount = 1;
VkResult result =
vkCreateImageView(g_vkDevice, &ivCI, nullptr, &g_vkSwapImageViews[i]);
if (result != VK_SUCCESS) {
debugVkResult("Failed to create swapchain image view", result);
return false;
}
}
if (!createDepthResources(g_vkSwapExtent.width, g_vkSwapExtent.height))
return false;
VkAttachmentDescription colorAttachment = {};
colorAttachment.format = g_vkSwapFormat;
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
VkAttachmentDescription depthAttachment = {};
depthAttachment.format = g_vkDepthFormat;
depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
VkAttachmentReference colorAttachmentRef = {};
colorAttachmentRef.attachment = 0;
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkAttachmentReference depthAttachmentRef = {};
depthAttachmentRef.attachment = 1;
depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
VkAttachmentDescription attachments[2] = {colorAttachment, depthAttachment};
VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef;
subpass.pDepthStencilAttachment = &depthAttachmentRef;
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_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
VkRenderPassCreateInfo rpCI = {};
rpCI.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
rpCI.attachmentCount = 2;
rpCI.pAttachments = attachments;
rpCI.subpassCount = 1;
rpCI.pSubpasses = &subpass;
rpCI.dependencyCount = 1;
rpCI.pDependencies = &dependency;
VkResult result = vkCreateRenderPass(g_vkDevice, &rpCI, nullptr, &g_vkRenderPass);
if (result != VK_SUCCESS) {
debugVkResult("Failed to create render pass", result);
return false;
}
VkDescriptorSetLayoutBinding triangleSamplerBinding = {};
triangleSamplerBinding.binding = 0;
triangleSamplerBinding.descriptorType =
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
triangleSamplerBinding.descriptorCount = 1;
triangleSamplerBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
triangleSamplerBinding.pImmutableSamplers = nullptr;
VkDescriptorSetLayoutCreateInfo triangleDslCI = {};
triangleDslCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
triangleDslCI.bindingCount = 1;
triangleDslCI.pBindings = &triangleSamplerBinding;
result = vkCreateDescriptorSetLayout(g_vkDevice, &triangleDslCI, nullptr,
&g_vkTriangleDescriptorSetLayout);
if (result != VK_SUCCESS ||
g_vkTriangleDescriptorSetLayout == VK_NULL_HANDLE) {
debugVkResult("Failed to create triangle descriptor set layout", result);
return false;
}
VkDescriptorPoolSize trianglePoolSize = {};
trianglePoolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
trianglePoolSize.descriptorCount = kMaxTriangleTextureDescriptors;
VkDescriptorPoolCreateInfo triangleDpCI = {};
triangleDpCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
triangleDpCI.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
triangleDpCI.maxSets = kMaxTriangleTextureDescriptors;
triangleDpCI.poolSizeCount = 1;
triangleDpCI.pPoolSizes = &trianglePoolSize;
result = vkCreateDescriptorPool(g_vkDevice, &triangleDpCI, nullptr,
&g_vkTriangleDescriptorPool);
if (result != VK_SUCCESS || g_vkTriangleDescriptorPool == VK_NULL_HANDLE) {
debugVkResult("Failed to create triangle descriptor pool", result);
return false;
}
VkPipelineLayoutCreateInfo plCI = {};
plCI.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
plCI.setLayoutCount = 1;
plCI.pSetLayouts = &g_vkTriangleDescriptorSetLayout;
VkPushConstantRange trianglePushRange = {};
trianglePushRange.stageFlags =
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
trianglePushRange.offset = 0;
trianglePushRange.size = sizeof(TrianglePushConstants);
plCI.pushConstantRangeCount = 1;
plCI.pPushConstantRanges = &trianglePushRange;
result = vkCreatePipelineLayout(g_vkDevice, &plCI, nullptr, &g_vkPipelineLayout);
if (result != VK_SUCCESS) {
debugVkResult("Failed to create pipeline layout", result);
return false;
}
g_vkTrianglePipelines.clear();
g_vkTrianglePipeline = createTrianglePipeline(
true, true, VK_COMPARE_OP_LESS_OR_EQUAL, false, VK_BLEND_FACTOR_ONE,
VK_BLEND_FACTOR_ZERO,
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
false, true);
if (g_vkTrianglePipeline == VK_NULL_HANDLE)
return false;
g_vkTrianglePipelines[makeTrianglePipelineKey(
true, true, VK_COMPARE_OP_LESS_OR_EQUAL, false, VK_BLEND_FACTOR_ONE,
VK_BLEND_FACTOR_ZERO,
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
false, true)] = g_vkTrianglePipeline;
VkVertexInputBindingDescription bindingDesc = {};
bindingDesc.binding = 0;
bindingDesc.stride = kVertexStridePF3TF2CB4NB4XW1;
bindingDesc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
VkVertexInputAttributeDescription attribs[3] = {};
attribs[0].location = 0;
attribs[0].binding = 0;
attribs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
attribs[0].offset = 0;
attribs[1].location = 1;
attribs[1].binding = 0;
attribs[1].format = VK_FORMAT_R32G32_SFLOAT;
attribs[1].offset = 12;
attribs[2].location = 2;
attribs[2].binding = 0;
attribs[2].format = VK_FORMAT_R8G8B8A8_UNORM;
attribs[2].offset = 20;
VkPipelineVertexInputStateCreateInfo viState = {};
viState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
viState.vertexBindingDescriptionCount = 1;
viState.pVertexBindingDescriptions = &bindingDesc;
viState.vertexAttributeDescriptionCount = 3;
viState.pVertexAttributeDescriptions = attribs;
VkPipelineInputAssemblyStateCreateInfo iaState = {};
iaState.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
iaState.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
iaState.primitiveRestartEnable = VK_FALSE;
VkViewport viewport = {};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = static_cast<float>(g_vkSwapExtent.width);
viewport.height = static_cast<float>(g_vkSwapExtent.height);
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
VkRect2D scissor = {};
scissor.offset = {0, 0};
scissor.extent = g_vkSwapExtent;
VkPipelineViewportStateCreateInfo vpState = {};
vpState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
vpState.viewportCount = 1;
vpState.pViewports = &viewport;
vpState.scissorCount = 1;
vpState.pScissors = &scissor;
VkPipelineRasterizationStateCreateInfo rsState = {};
rsState.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rsState.depthClampEnable = VK_FALSE;
rsState.rasterizerDiscardEnable = VK_FALSE;
rsState.polygonMode = VK_POLYGON_MODE_FILL;
rsState.cullMode = VK_CULL_MODE_NONE;
rsState.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
rsState.depthBiasEnable = VK_FALSE;
rsState.lineWidth = 1.0f;
VkPipelineMultisampleStateCreateInfo msState = {};
msState.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
msState.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
VkDescriptorSetLayoutBinding samplerBinding = {};
samplerBinding.binding = 0;
samplerBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
samplerBinding.descriptorCount = 1;
samplerBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
samplerBinding.pImmutableSamplers = nullptr;
VkDescriptorSetLayoutCreateInfo dslCI = {};
dslCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
dslCI.bindingCount = 1;
dslCI.pBindings = &samplerBinding;
result = vkCreateDescriptorSetLayout(g_vkDevice, &dslCI, nullptr,
&g_vkUiDescriptorSetLayout);
if (result != VK_SUCCESS || g_vkUiDescriptorSetLayout == VK_NULL_HANDLE) {
debugVkResult("Failed to create UI descriptor set layout", result);
return false;
}
VkDescriptorPoolSize poolSize = {};
poolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
poolSize.descriptorCount = 1;
VkDescriptorPoolCreateInfo dpCI = {};
dpCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
dpCI.maxSets = 1;
dpCI.poolSizeCount = 1;
dpCI.pPoolSizes = &poolSize;
result = vkCreateDescriptorPool(g_vkDevice, &dpCI, nullptr,
&g_vkUiDescriptorPool);
if (result != VK_SUCCESS || g_vkUiDescriptorPool == VK_NULL_HANDLE) {
debugVkResult("Failed to create UI descriptor pool", result);
return false;
}
VkDescriptorSetAllocateInfo dsAI = {};
dsAI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
dsAI.descriptorPool = g_vkUiDescriptorPool;
dsAI.descriptorSetCount = 1;
dsAI.pSetLayouts = &g_vkUiDescriptorSetLayout;
result = vkAllocateDescriptorSets(g_vkDevice, &dsAI, &g_vkUiDescriptorSet);
if (result != VK_SUCCESS || g_vkUiDescriptorSet == VK_NULL_HANDLE) {
debugVkResult("Failed to allocate UI descriptor set", result);
return false;
}
VkPipelineLayoutCreateInfo uiPlCI = {};
uiPlCI.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
uiPlCI.setLayoutCount = 1;
uiPlCI.pSetLayouts = &g_vkUiDescriptorSetLayout;
VkPushConstantRange uiPushRange = {};
uiPushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
uiPushRange.offset = 0;
uiPushRange.size = sizeof(float) * 16;
uiPlCI.pushConstantRangeCount = 1;
uiPlCI.pPushConstantRanges = &uiPushRange;
result = vkCreatePipelineLayout(g_vkDevice, &uiPlCI, nullptr,
&g_vkUiPipelineLayout);
if (result != VK_SUCCESS || g_vkUiPipelineLayout == VK_NULL_HANDLE) {
debugVkResult("Failed to create UI pipeline layout", result);
return false;
}
VkShaderModule uiVertModule =
createShaderModule(kUiCompositeVertSpv,
sizeof(kUiCompositeVertSpv) / sizeof(kUiCompositeVertSpv[0]));
VkShaderModule uiFragModule =
createShaderModule(kUiCompositeFragSpv,
sizeof(kUiCompositeFragSpv) / sizeof(kUiCompositeFragSpv[0]));
if (uiVertModule == VK_NULL_HANDLE || uiFragModule == VK_NULL_HANDLE) {
if (uiVertModule != VK_NULL_HANDLE)
vkDestroyShaderModule(g_vkDevice, uiVertModule, nullptr);
if (uiFragModule != VK_NULL_HANDLE)
vkDestroyShaderModule(g_vkDevice, uiFragModule, nullptr);
return false;
}
VkPipelineShaderStageCreateInfo uiStages[2] = {};
uiStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
uiStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
uiStages[0].module = uiVertModule;
uiStages[0].pName = "main";
uiStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
uiStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
uiStages[1].module = uiFragModule;
uiStages[1].pName = "main";
VkPipelineColorBlendAttachmentState uiBlendAttachment = {};
uiBlendAttachment.blendEnable = VK_TRUE;
uiBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
uiBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
uiBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
uiBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
uiBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
uiBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
uiBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT |
VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT |
VK_COLOR_COMPONENT_A_BIT;
VkPipelineColorBlendStateCreateInfo uiCbState = {};
uiCbState.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
uiCbState.attachmentCount = 1;
uiCbState.pAttachments = &uiBlendAttachment;
VkPipelineDepthStencilStateCreateInfo uiDepthState = {};
uiDepthState.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
uiDepthState.depthTestEnable = VK_FALSE;
uiDepthState.depthWriteEnable = VK_FALSE;
uiDepthState.depthCompareOp = VK_COMPARE_OP_ALWAYS;
uiDepthState.depthBoundsTestEnable = VK_FALSE;
uiDepthState.stencilTestEnable = VK_FALSE;
VkGraphicsPipelineCreateInfo uiGpCI = {};
uiGpCI.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
uiGpCI.stageCount = 2;
uiGpCI.pStages = uiStages;
uiGpCI.pVertexInputState = &viState;
uiGpCI.pInputAssemblyState = &iaState;
uiGpCI.pViewportState = &vpState;
uiGpCI.pRasterizationState = &rsState;
uiGpCI.pMultisampleState = &msState;
uiGpCI.pDepthStencilState = &uiDepthState;
uiGpCI.pColorBlendState = &uiCbState;
uiGpCI.layout = g_vkUiPipelineLayout;
uiGpCI.renderPass = g_vkRenderPass;
uiGpCI.subpass = 0;
result = vkCreateGraphicsPipelines(g_vkDevice, VK_NULL_HANDLE, 1, &uiGpCI,
nullptr, &g_vkUiPipeline);
vkDestroyShaderModule(g_vkDevice, uiVertModule, nullptr);
vkDestroyShaderModule(g_vkDevice, uiFragModule, nullptr);
if (result != VK_SUCCESS || g_vkUiPipeline == VK_NULL_HANDLE) {
debugVkResult("Failed to create UI graphics pipeline", result);
return false;
}
if (g_vkUiImageView != VK_NULL_HANDLE && g_vkUiSampler != VK_NULL_HANDLE &&
g_vkUiImageLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
updateUiDescriptorSet();
}
g_vkFramebuffers.assign(g_vkSwapImageViews.size(), VK_NULL_HANDLE);
for (size_t i = 0; i < g_vkSwapImageViews.size(); ++i) {
VkImageView attachments[] = {g_vkSwapImageViews[i], g_vkDepthImageView};
VkFramebufferCreateInfo fbCI = {};
fbCI.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
fbCI.renderPass = g_vkRenderPass;
fbCI.attachmentCount = 2;
fbCI.pAttachments = attachments;
fbCI.width = g_vkSwapExtent.width;
fbCI.height = g_vkSwapExtent.height;
fbCI.layers = 1;
result = vkCreateFramebuffer(g_vkDevice, &fbCI, nullptr, &g_vkFramebuffers[i]);
if (result != VK_SUCCESS) {
debugVkResult("Failed to create framebuffer", result);
return false;
}
}
return true;
}
// ============================================================================
// Identity matrix helper
// ============================================================================
static void mat4_identity(float *m) {
memset(m, 0, 16 * sizeof(float));
m[0] = m[5] = m[10] = m[15] = 1.0f;
}
static void mat4_multiply(float *out, const float *a, const float *b) {
float tmp[16];
// Column-major matrix multiply (OpenGL convention):
// out = a * b, index = row + col*4.
for (int col = 0; col < 4; ++col) {
for (int row = 0; row < 4; ++row) {
float sum = 0.0f;
for (int k = 0; k < 4; ++k) {
sum += a[row + k * 4] * b[k + col * 4];
}
tmp[row + col * 4] = sum;
}
}
memcpy(out, tmp, 16 * sizeof(float));
}
static bool mat4_inverse(float *out, const float *m) {
float inv[16];
inv[0] = m[5] * m[10] * m[15] - m[5] * m[11] * m[14] - m[9] * m[6] * m[15] +
m[9] * m[7] * m[14] + m[13] * m[6] * m[11] -
m[13] * m[7] * m[10];
inv[4] = -m[4] * m[10] * m[15] + m[4] * m[11] * m[14] +
m[8] * m[6] * m[15] - m[8] * m[7] * m[14] - m[12] * m[6] * m[11] +
m[12] * m[7] * m[10];
inv[8] = m[4] * m[9] * m[15] - m[4] * m[11] * m[13] - m[8] * m[5] * m[15] +
m[8] * m[7] * m[13] + m[12] * m[5] * m[11] -
m[12] * m[7] * m[9];
inv[12] = -m[4] * m[9] * m[14] + m[4] * m[10] * m[13] +
m[8] * m[5] * m[14] - m[8] * m[6] * m[13] -
m[12] * m[5] * m[10] + m[12] * m[6] * m[9];
inv[1] = -m[1] * m[10] * m[15] + m[1] * m[11] * m[14] +
m[9] * m[2] * m[15] - m[9] * m[3] * m[14] - m[13] * m[2] * m[11] +
m[13] * m[3] * m[10];
inv[5] = m[0] * m[10] * m[15] - m[0] * m[11] * m[14] - m[8] * m[2] * m[15] +
m[8] * m[3] * m[14] + m[12] * m[2] * m[11] -
m[12] * m[3] * m[10];
inv[9] = -m[0] * m[9] * m[15] + m[0] * m[11] * m[13] +
m[8] * m[1] * m[15] - m[8] * m[3] * m[13] - m[12] * m[1] * m[11] +
m[12] * m[3] * m[9];
inv[13] = m[0] * m[9] * m[14] - m[0] * m[10] * m[13] -
m[8] * m[1] * m[14] + m[8] * m[2] * m[13] +
m[12] * m[1] * m[10] - m[12] * m[2] * m[9];
inv[2] = m[1] * m[6] * m[15] - m[1] * m[7] * m[14] - m[5] * m[2] * m[15] +
m[5] * m[3] * m[14] + m[13] * m[2] * m[7] - m[13] * m[3] * m[6];
inv[6] = -m[0] * m[6] * m[15] + m[0] * m[7] * m[14] +
m[4] * m[2] * m[15] - m[4] * m[3] * m[14] - m[12] * m[2] * m[7] +
m[12] * m[3] * m[6];
inv[10] = m[0] * m[5] * m[15] - m[0] * m[7] * m[13] -
m[4] * m[1] * m[15] + m[4] * m[3] * m[13] +
m[12] * m[1] * m[7] - m[12] * m[3] * m[5];
inv[14] = -m[0] * m[5] * m[14] + m[0] * m[6] * m[13] +
m[4] * m[1] * m[14] - m[4] * m[2] * m[13] -
m[12] * m[1] * m[6] + m[12] * m[2] * m[5];
inv[3] = -m[1] * m[6] * m[11] + m[1] * m[7] * m[10] +
m[5] * m[2] * m[11] - m[5] * m[3] * m[10] - m[9] * m[2] * m[7] +
m[9] * m[3] * m[6];
inv[7] = m[0] * m[6] * m[11] - m[0] * m[7] * m[10] - m[4] * m[2] * m[11] +
m[4] * m[3] * m[10] + m[8] * m[2] * m[7] - m[8] * m[3] * m[6];
inv[11] = -m[0] * m[5] * m[11] + m[0] * m[7] * m[9] +
m[4] * m[1] * m[11] - m[4] * m[3] * m[9] - m[8] * m[1] * m[7] +
m[8] * m[3] * m[5];
inv[15] = m[0] * m[5] * m[10] - m[0] * m[6] * m[9] - m[4] * m[1] * m[10] +
m[4] * m[2] * m[9] + m[8] * m[1] * m[6] - m[8] * m[2] * m[5];
const float det =
m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12];
if (std::fabs(det) <= 1.0e-8f) {
return false;
}
const float invDet = 1.0f / det;
for (int i = 0; i < 16; ++i) {
out[i] = inv[i] * invDet;
}
return true;
}
static float *curMatrix() {
ensureThreadLocalMatrixStacksInitialised();
return g_matStacks[g_curMatrixMode].stack[g_matStacks[g_curMatrixMode].top];
}
static void appendOverlayQuad(std::vector<BootstrapVertex> &verts, float x0,
float y0, float x1, float y1, uint8_t r,
uint8_t g, uint8_t b, uint8_t a) {
BootstrapVertex v0 = {x0, y0, 0.0f, 0.0f, 0.0f, r, g, b, a, 0, 0, 0, 0, 0};
BootstrapVertex v1 = {x1, y0, 0.0f, 1.0f, 0.0f, r, g, b, a, 0, 0, 0, 0, 0};
BootstrapVertex v2 = {x1, y1, 0.0f, 1.0f, 1.0f, r, g, b, a, 0, 0, 0, 0, 0};
BootstrapVertex v3 = {x0, y1, 0.0f, 0.0f, 1.0f, r, g, b, a, 0, 0, 0, 0, 0};
verts.push_back(v0);
verts.push_back(v1);
verts.push_back(v2);
verts.push_back(v2);
verts.push_back(v3);
verts.push_back(v0);
}
static const char *const *overlayGlyph5x7Rows(char c) {
static const char *const g0[7] = {"01110", "10001", "10011", "10101",
"11001", "10001", "01110"};
static const char *const g1[7] = {"00100", "01100", "00100", "00100",
"00100", "00100", "01110"};
static const char *const g2[7] = {"01110", "10001", "00001", "00010",
"00100", "01000", "11111"};
static const char *const g3[7] = {"11110", "00001", "00001", "01110",
"00001", "00001", "11110"};
static const char *const g4[7] = {"00010", "00110", "01010", "10010",
"11111", "00010", "00010"};
static const char *const g5[7] = {"11111", "10000", "10000", "11110",
"00001", "00001", "11110"};
static const char *const g6[7] = {"01110", "10000", "10000", "11110",
"10001", "10001", "01110"};
static const char *const g7[7] = {"11111", "00001", "00010", "00100",
"01000", "01000", "01000"};
static const char *const g8[7] = {"01110", "10001", "10001", "01110",
"10001", "10001", "01110"};
static const char *const g9[7] = {"01110", "10001", "10001", "01111",
"00001", "00001", "01110"};
static const char *const gF[7] = {"11111", "10000", "10000", "11110",
"10000", "10000", "10000"};
static const char *const gD[7] = {"11110", "10001", "10001", "10001",
"10001", "10001", "11110"};
static const char *const gV[7] = {"10001", "10001", "10001", "10001",
"01010", "01010", "00100"};
static const char *const gU[7] = {"10001", "10001", "10001", "10001",
"10001", "10001", "01110"};
static const char *const gMinus[7] = {"00000", "00000", "00000", "11111",
"00000", "00000", "00000"};
switch (c) {
case '0':
return g0;
case '1':
return g1;
case '2':
return g2;
case '3':
return g3;
case '4':
return g4;
case '5':
return g5;
case '6':
return g6;
case '7':
return g7;
case '8':
return g8;
case '9':
return g9;
case 'F':
return gF;
case 'D':
return gD;
case 'V':
return gV;
case 'U':
return gU;
case '-':
return gMinus;
default:
break;
}
return nullptr;
}
static void appendOverlayText5x7(std::vector<BootstrapVertex> &verts,
const char *text, float x, float y, float cellW,
float cellH, uint8_t r, uint8_t g, uint8_t b,
uint8_t a) {
if (text == nullptr)
return;
float cursorX = x;
for (const char *p = text; *p != '\0'; ++p) {
if (*p == ' ') {
cursorX += cellW * 6.0f;
continue;
}
const char *const *rows = overlayGlyph5x7Rows(*p);
if (rows != nullptr) {
for (int row = 0; row < 7; ++row) {
for (int col = 0; col < 5; ++col) {
if (rows[row][col] != '1')
continue;
const float x0 = cursorX + static_cast<float>(col) * cellW;
const float y0 = y + static_cast<float>(row) * cellH;
const float x1 = x0 + cellW * 0.86f;
const float y1 = y0 + cellH * 0.86f;
appendOverlayQuad(verts, x0, y0, x1, y1, r, g, b, a);
}
}
}
cursorX += cellW * 6.0f;
}
}
static void queueCornerOverlayText() {
if (!g_vkShowCornerOverlay)
return;
std::vector<BootstrapVertex> verts;
verts.reserve(1400);
const float xStart = -0.985f;
const float cellW = 0.018f;
const float cellH = 0.030f;
const char *glyphV[7] = {"10001", "10001", "10001", "10001",
"01010", "01010", "00100"};
const char *glyphK[7] = {"10001", "10010", "10100", "11000",
"10100", "10010", "10001"};
auto emitGlyph = [&](const char *rows[7], float xOffset, float yStart) {
for (int row = 0; row < 7; ++row) {
for (int col = 0; col < 5; ++col) {
if (rows[row][col] != '1')
continue;
const float x0 = xStart + (xOffset + static_cast<float>(col)) * cellW;
const float y0 = yStart + static_cast<float>(row) * cellH;
const float x1 = x0 + cellW * 0.85f;
const float y1 = y0 + cellH * 0.85f;
appendOverlayQuad(verts, x0, y0, x1, y1, 255, 230, 40, 255);
}
}
};
auto emitBadgeAtY = [&](float yStart) {
appendOverlayQuad(verts, xStart - 0.01f, yStart - 0.01f,
xStart + cellW * 13.0f, yStart + cellH * 8.0f, 0, 0, 0,
180);
emitGlyph(glyphV, 0.0f, yStart);
emitGlyph(glyphK, 7.0f, yStart);
};
emitBadgeAtY(-0.945f);
emitBadgeAtY(0.705f);
if (g_vkShowPerfOverlay) {
const float px = xStart - 0.01f;
const float py = -0.675f;
const float pw = cellW * 20.0f;
const float ph = cellH * 10.8f;
appendOverlayQuad(verts, px, py, px + pw, py + ph, 0, 0, 0, 170);
char fpsText[16];
char drawText[16];
char vertKText[16];
char uploadKBText[16];
std::snprintf(fpsText, sizeof(fpsText), "%u",
static_cast<unsigned int>(g_vkPerfFpsSmoothed + 0.5f));
std::snprintf(drawText, sizeof(drawText), "%u", g_vkPerfDrawCount);
std::snprintf(vertKText, sizeof(vertKText), "%u", g_vkPerfVertexCount / 1000u);
std::snprintf(uploadKBText, sizeof(uploadKBText), "%u",
g_vkPerfUploadBytes / 1024u);
const float sx = xStart + 0.005f;
const float sy = py + 0.010f;
const float sw = cellW * 0.64f;
const float sh = cellH * 0.44f;
const float dy = cellH * 2.2f;
appendOverlayText5x7(verts, "F", sx, sy + dy * 0.0f, sw, sh, 255, 220, 60, 255);
appendOverlayText5x7(verts, fpsText, sx + sw * 8.0f, sy + dy * 0.0f, sw, sh,
230, 230, 230, 255);
appendOverlayText5x7(verts, "D", sx, sy + dy * 1.0f, sw, sh, 255, 220, 60, 255);
appendOverlayText5x7(verts, drawText, sx + sw * 8.0f, sy + dy * 1.0f, sw, sh,
230, 230, 230, 255);
appendOverlayText5x7(verts, "V", sx, sy + dy * 2.0f, sw, sh, 255, 220, 60, 255);
appendOverlayText5x7(verts, vertKText, sx + sw * 8.0f, sy + dy * 2.0f, sw, sh,
230, 230, 230, 255);
appendOverlayText5x7(verts, "U", sx, sy + dy * 3.0f, sw, sh, 255, 220, 60, 255);
appendOverlayText5x7(verts, uploadKBText, sx + sw * 8.0f, sy + dy * 3.0f, sw, sh,
230, 230, 230, 255);
}
if (verts.empty())
return;
const size_t oldSize = g_vkFrameVertexData.size();
const size_t addBytes = verts.size() * sizeof(BootstrapVertex);
g_vkFrameVertexData.resize(oldSize + addBytes);
std::memcpy(g_vkFrameVertexData.data() + oldSize, verts.data(), addBytes);
VulkanQueuedDraw draw = {};
draw.firstVertex =
static_cast<uint32_t>(oldSize / kVertexStridePF3TF2CB4NB4XW1);
draw.vertexCount = static_cast<uint32_t>(verts.size());
mat4_identity(draw.mvp);
draw.depthTestEnable = false;
draw.depthWriteEnable = false;
draw.depthCompareOp = VK_COMPARE_OP_ALWAYS;
draw.blendEnable = false;
draw.srcBlendFactor = VK_BLEND_FACTOR_ONE;
draw.dstBlendFactor = VK_BLEND_FACTOR_ZERO;
draw.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
draw.cullEnable = false;
draw.cullClockwise = true;
draw.blendConstants[0] = 1.0f;
draw.blendConstants[1] = 1.0f;
draw.blendConstants[2] = 1.0f;
draw.blendConstants[3] = 1.0f;
g_vkQueuedDraws.push_back(draw);
}
static void appendUiCompositeFullscreenQuad(uint32_t &firstVertexOut,
uint32_t &vertexCountOut) {
firstVertexOut = 0;
vertexCountOut = 0;
if (!g_vkUiImageReady)
return;
const BootstrapVertex verts[6] = {
{-1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 255, 255, 255, 255, 0, 0, 0, 0, 0},
{+1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 255, 255, 255, 255, 0, 0, 0, 0, 0},
{+1.0f, +1.0f, 0.0f, 1.0f, 0.0f, 255, 255, 255, 255, 0, 0, 0, 0, 0},
{+1.0f, +1.0f, 0.0f, 1.0f, 0.0f, 255, 255, 255, 255, 0, 0, 0, 0, 0},
{-1.0f, +1.0f, 0.0f, 0.0f, 0.0f, 255, 255, 255, 255, 0, 0, 0, 0, 0},
{-1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 255, 255, 255, 255, 0, 0, 0, 0, 0},
};
const size_t oldSize = g_vkFrameVertexData.size();
const size_t addBytes = sizeof(verts);
g_vkFrameVertexData.resize(oldSize + addBytes);
std::memcpy(g_vkFrameVertexData.data() + oldSize, verts, addBytes);
firstVertexOut =
static_cast<uint32_t>(oldSize / kVertexStridePF3TF2CB4NB4XW1);
vertexCountOut = 6;
}
// ============================================================================
// C4JRender — Matrix stack
// ============================================================================
void C4JRender::MatrixMode(int type) {
ensureThreadLocalMatrixStacksInitialised();
if (type >= 0 && type < NUM_MATRIX_MODES)
g_curMatrixMode = type;
}
void C4JRender::MatrixSetIdentity() {
mat4_identity(curMatrix());
g_matrixDirty = true;
}
void C4JRender::MatrixTranslate(float x, float y, float z) {
float t[16];
mat4_identity(t);
t[12] = x;
t[13] = y;
t[14] = z;
mat4_multiply(curMatrix(), curMatrix(), t);
g_matrixDirty = true;
}
void C4JRender::MatrixRotate(float angle, float x, float y, float z) {
float c = cosf(angle), s = sinf(angle);
float len = sqrtf(x * x + y * y + z * z);
if (len < 1e-6f)
return;
x /= len;
y /= len;
z /= len;
float t = 1.0f - c;
float r[16];
mat4_identity(r);
r[0] = t * x * x + c;
r[4] = t * x * y - s * z;
r[8] = t * x * z + s * y;
r[1] = t * x * y + s * z;
r[5] = t * y * y + c;
r[9] = t * y * z - s * x;
r[2] = t * x * z - s * y;
r[6] = t * y * z + s * x;
r[10] = t * z * z + c;
mat4_multiply(curMatrix(), curMatrix(), r);
g_matrixDirty = true;
}
void C4JRender::MatrixScale(float x, float y, float z) {
float s[16];
mat4_identity(s);
s[0] = x;
s[5] = y;
s[10] = z;
mat4_multiply(curMatrix(), curMatrix(), s);
g_matrixDirty = true;
}
void C4JRender::MatrixPerspective(float fovy, float aspect, float zNear,
float zFar) {
// gluPerspective-style input is degrees.
const float fovyRadians = fovy * (3.14159265358979323846f / 180.0f);
float f = 1.0f / tanf(fovyRadians * 0.5f);
float p[16];
memset(p, 0, sizeof(p));
p[0] = f / aspect;
p[5] = f;
p[10] = (zFar + zNear) / (zNear - zFar);
p[11] = -1.0f;
p[14] = (2.0f * zFar * zNear) / (zNear - zFar);
mat4_multiply(curMatrix(), curMatrix(), p);
g_matrixDirty = true;
}
void C4JRender::MatrixOrthogonal(float left, float right, float bottom,
float top, float zNear, float zFar) {
float o[16];
memset(o, 0, sizeof(o));
o[0] = 2.0f / (right - left);
o[5] = 2.0f / (top - bottom);
o[10] = -2.0f / (zFar - zNear);
o[12] = -(right + left) / (right - left);
o[13] = -(top + bottom) / (top - bottom);
o[14] = -(zFar + zNear) / (zFar - zNear);
o[15] = 1.0f;
mat4_multiply(curMatrix(), curMatrix(), o);
g_matrixDirty = true;
}
void C4JRender::MatrixPop() {
auto &s = g_matStacks[g_curMatrixMode];
if (s.top > 0)
s.top--;
g_matrixDirty = true;
}
void C4JRender::MatrixPush() {
auto &s = g_matStacks[g_curMatrixMode];
if (s.top < MATRIX_STACK_DEPTH - 1) {
memcpy(s.stack[s.top + 1], s.stack[s.top], 16 * sizeof(float));
s.top++;
}
g_matrixDirty = true;
}
void C4JRender::MatrixMult(float *mat) {
mat4_multiply(curMatrix(), curMatrix(), mat);
g_matrixDirty = true;
}
const float *C4JRender::MatrixGet(int type) {
ensureThreadLocalMatrixStacksInitialised();
if (type >= 0 && type < NUM_MATRIX_MODES)
return g_matStacks[type].stack[g_matStacks[type].top];
return g_matStacks[0].stack[g_matStacks[0].top];
}
void C4JRender::Set_matrixDirty() { g_matrixDirty = true; }
// ============================================================================
// C4JRender - Core (Vulkan init and present)
// ============================================================================
void C4JRender::Initialise(HWND hWnd, int width, int height) {
g_vkWindow = hWnd;
destroyVulkanRuntime();
if (width <= 0 || height <= 0) {
int clientW = 0;
int clientH = 0;
if (getWindowClientSize(hWnd, clientW, clientH)) {
width = clientW;
height = clientH;
}
}
if (width <= 0 || height <= 0) {
debugVk("C4JRender_Vulkan: Skipping init for zero-sized window.\n");
return;
}
// Init matrix stacks
ensureThreadLocalMatrixStacksInitialised();
for (int i = 0; i < NUM_MATRIX_MODES; i++) {
g_matStacks[i].top = 0;
mat4_identity(g_matStacks[i].stack[0]);
}
resetThreadLocalRenderState();
// ---- VkInstance ----
VkApplicationInfo appInfo = {};
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
appInfo.pApplicationName = "MinecraftConsoles";
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.pEngineName = "4JRender_Vulkan";
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.apiVersion = VK_API_VERSION_1_2;
const char *extensions[] = {
VK_KHR_SURFACE_EXTENSION_NAME,
VK_KHR_WIN32_SURFACE_EXTENSION_NAME,
};
VkInstanceCreateInfo ci = {};
ci.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
ci.pApplicationInfo = &appInfo;
ci.enabledExtensionCount = 2;
ci.ppEnabledExtensionNames = extensions;
#ifdef _DEBUG
const char *layers[] = {"VK_LAYER_KHRONOS_validation"};
if (hasValidationLayer(layers[0])) {
ci.enabledLayerCount = 1;
ci.ppEnabledLayerNames = layers;
} else {
debugVk("C4JRender_Vulkan: Validation layer not available; continuing "
"without it.\n");
}
#endif
VkResult result = vkCreateInstance(&ci, nullptr, &g_vkInstance);
if (result != VK_SUCCESS) {
debugVkResult("Failed to create VkInstance", result);
return;
}
// ---- Surface ----
VkWin32SurfaceCreateInfoKHR surfCI = {};
surfCI.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
surfCI.hinstance = GetModuleHandle(NULL);
surfCI.hwnd = hWnd;
result = vkCreateWin32SurfaceKHR(g_vkInstance, &surfCI, nullptr, &g_vkSurface);
if (result != VK_SUCCESS) {
debugVkResult("Failed to create Win32 surface", result);
return;
}
// ---- Physical device ----
uint32_t devCount = 0;
result = vkEnumeratePhysicalDevices(g_vkInstance, &devCount, nullptr);
if (result != VK_SUCCESS || devCount == 0) {
debugVk("C4JRender_Vulkan: No Vulkan physical devices found.\n");
return;
}
std::vector<VkPhysicalDevice> devices(devCount);
result = vkEnumeratePhysicalDevices(g_vkInstance, &devCount, devices.data());
if (result != VK_SUCCESS) {
debugVkResult("Failed to enumerate Vulkan physical devices", result);
return;
}
bool foundDeviceAndQueue = false;
for (uint32_t d = 0; d < devCount && !foundDeviceAndQueue; ++d) {
VkPhysicalDevice candidate = devices[d];
uint32_t qfCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(candidate, &qfCount, nullptr);
if (qfCount == 0)
continue;
std::vector<VkQueueFamilyProperties> qfProps(qfCount);
vkGetPhysicalDeviceQueueFamilyProperties(candidate, &qfCount,
qfProps.data());
for (uint32_t i = 0; i < qfCount; i++) {
VkBool32 presentSupport = VK_FALSE;
result = vkGetPhysicalDeviceSurfaceSupportKHR(candidate, i, g_vkSurface,
&presentSupport);
if (result != VK_SUCCESS)
continue;
if ((qfProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && presentSupport) {
g_vkPhysicalDevice = candidate;
g_vkGraphicsFamily = i;
foundDeviceAndQueue = true;
break;
}
}
}
if (!foundDeviceAndQueue || g_vkPhysicalDevice == VK_NULL_HANDLE) {
debugVk("C4JRender_Vulkan: Failed to find graphics+present queue family.\n");
return;
}
// ---- Logical device ----
float queuePriority = 1.0f;
VkDeviceQueueCreateInfo qCI = {};
qCI.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
qCI.queueFamilyIndex = g_vkGraphicsFamily;
qCI.queueCount = 1;
qCI.pQueuePriorities = &queuePriority;
const char *devExts[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
VkDeviceCreateInfo devCI = {};
devCI.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
devCI.queueCreateInfoCount = 1;
devCI.pQueueCreateInfos = &qCI;
devCI.enabledExtensionCount = 1;
devCI.ppEnabledExtensionNames = devExts;
result = vkCreateDevice(g_vkPhysicalDevice, &devCI, nullptr, &g_vkDevice);
if (result != VK_SUCCESS) {
debugVkResult("Failed to create VkDevice", result);
return;
}
vkGetDeviceQueue(g_vkDevice, g_vkGraphicsFamily, 0, &g_vkGraphicsQueue);
if (g_vkGraphicsQueue == VK_NULL_HANDLE) {
debugVk("C4JRender_Vulkan: Failed to get graphics queue.\n");
return;
}
// ---- Swapchain ----
VkSurfaceCapabilitiesKHR caps;
result = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(g_vkPhysicalDevice,
g_vkSurface, &caps);
if (result != VK_SUCCESS) {
debugVkResult("Failed to query surface capabilities", result);
return;
}
uint32_t formatCount = 0;
result =
vkGetPhysicalDeviceSurfaceFormatsKHR(g_vkPhysicalDevice, g_vkSurface,
&formatCount, nullptr);
if (result != VK_SUCCESS || formatCount == 0) {
debugVk("C4JRender_Vulkan: No swapchain surface formats available.\n");
return;
}
std::vector<VkSurfaceFormatKHR> formats(formatCount);
result = vkGetPhysicalDeviceSurfaceFormatsKHR(g_vkPhysicalDevice, g_vkSurface,
&formatCount, formats.data());
if (result != VK_SUCCESS) {
debugVkResult("Failed to query surface formats", result);
return;
}
VkSurfaceFormatKHR chosenFormat = formats[0];
for (const auto &f : formats) {
if (f.format == VK_FORMAT_B8G8R8A8_UNORM &&
f.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
chosenFormat = f;
break;
}
}
g_vkSwapFormat = chosenFormat.format;
g_vkSwapExtent.width = (uint32_t)width;
g_vkSwapExtent.height = (uint32_t)height;
if (caps.currentExtent.width != UINT32_MAX) {
g_vkSwapExtent = caps.currentExtent;
}
uint32_t imageCount = caps.minImageCount + 1;
if (caps.maxImageCount > 0 && imageCount > caps.maxImageCount)
imageCount = caps.maxImageCount;
VkSwapchainCreateInfoKHR scCI = {};
scCI.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
scCI.surface = g_vkSurface;
scCI.minImageCount = imageCount;
scCI.imageFormat = chosenFormat.format;
scCI.imageColorSpace = chosenFormat.colorSpace;
scCI.imageExtent = g_vkSwapExtent;
scCI.imageArrayLayers = 1;
if (!(caps.supportedUsageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)) {
debugVk("C4JRender_Vulkan: Surface does not support COLOR_ATTACHMENT usage.\n");
return;
}
scCI.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
scCI.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
scCI.preTransform = caps.currentTransform;
if (caps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) {
scCI.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
}
if (caps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) {
scCI.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
} else if (caps.supportedCompositeAlpha &
VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR) {
scCI.compositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR;
} else if (caps.supportedCompositeAlpha &
VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR) {
scCI.compositeAlpha = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR;
} else {
scCI.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
}
scCI.presentMode = VK_PRESENT_MODE_FIFO_KHR;
scCI.clipped = VK_TRUE;
result = vkCreateSwapchainKHR(g_vkDevice, &scCI, nullptr, &g_vkSwapchain);
if (result != VK_SUCCESS) {
debugVkResult("Failed to create swapchain", result);
return;
}
uint32_t swapImgCount = 0;
result =
vkGetSwapchainImagesKHR(g_vkDevice, g_vkSwapchain, &swapImgCount, nullptr);
if (result != VK_SUCCESS || swapImgCount == 0) {
debugVk("C4JRender_Vulkan: Failed to query swapchain image count.\n");
return;
}
g_vkSwapImages.resize(swapImgCount);
result = vkGetSwapchainImagesKHR(g_vkDevice, g_vkSwapchain, &swapImgCount,
g_vkSwapImages.data());
if (result != VK_SUCCESS) {
debugVkResult("Failed to get swapchain images", result);
return;
}
g_vkSwapImageLayouts.assign(swapImgCount, VK_IMAGE_LAYOUT_UNDEFINED);
if (!createTriangleRenderResources()) {
debugVk("C4JRender_Vulkan: Failed to create triangle render resources.\n");
return;
}
// ---- Command pool + buffer ----
VkCommandPoolCreateInfo cpCI = {};
cpCI.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
cpCI.queueFamilyIndex = g_vkGraphicsFamily;
cpCI.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
result = vkCreateCommandPool(g_vkDevice, &cpCI, nullptr, &g_vkCommandPool);
if (result != VK_SUCCESS || g_vkCommandPool == VK_NULL_HANDLE) {
debugVkResult("Failed to create command pool", result);
return;
}
VkCommandBufferAllocateInfo cbAI = {};
cbAI.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
cbAI.commandPool = g_vkCommandPool;
cbAI.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
cbAI.commandBufferCount = 1;
result = vkAllocateCommandBuffers(g_vkDevice, &cbAI, &g_vkCommandBuffer);
if (result != VK_SUCCESS || g_vkCommandBuffer == VK_NULL_HANDLE) {
debugVkResult("Failed to allocate command buffer", result);
return;
}
// ---- Sync objects ----
VkFenceCreateInfo fCI = {};
fCI.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fCI.flags = VK_FENCE_CREATE_SIGNALED_BIT;
result = vkCreateFence(g_vkDevice, &fCI, nullptr, &g_vkFence);
if (result != VK_SUCCESS || g_vkFence == VK_NULL_HANDLE) {
debugVkResult("Failed to create fence", result);
return;
}
VkSemaphoreCreateInfo sCI = {};
sCI.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
result = vkCreateSemaphore(g_vkDevice, &sCI, nullptr, &g_vkImageAvailable);
if (result != VK_SUCCESS || g_vkImageAvailable == VK_NULL_HANDLE) {
debugVkResult("Failed to create image-available semaphore", result);
return;
}
result = vkCreateSemaphore(g_vkDevice, &sCI, nullptr, &g_vkRenderFinished);
if (result != VK_SUCCESS || g_vkRenderFinished == VK_NULL_HANDLE) {
debugVkResult("Failed to create render-finished semaphore", result);
return;
}
if (!ensureWhiteTexture()) {
debugVk("C4JRender_Vulkan: Failed to create fallback white texture.\n");
return;
}
g_vkInitialized = true;
g_isWidescreen = (height > 0) ? (((float)width / (float)height) > 1.5f) : true;
debugVk("C4JRender_Vulkan: Initialised OK!\n");
}
void C4JRender::InitialiseContext() {}
void C4JRender::Tick() {}
void C4JRender::UpdateGamma(unsigned short) {}
void C4JRender::StartFrame() {
g_vkFrameVertexData.clear();
g_vkQueuedDraws.clear();
if (g_vkFrameVertexData.capacity() < g_vkFrameVertexReserveBytes) {
g_vkFrameVertexData.reserve(g_vkFrameVertexReserveBytes);
}
if (g_vkQueuedDraws.capacity() < g_vkFrameDrawReserveCount) {
g_vkQueuedDraws.reserve(g_vkFrameDrawReserveCount);
}
if (!g_vkInitialized)
return;
VkResult result = vkWaitForFences(g_vkDevice, 1, &g_vkFence, VK_TRUE,
UINT64_MAX);
if (result != VK_SUCCESS) {
debugVkResult("vkWaitForFences failed", result);
g_vkInitialized = false;
return;
}
processPendingTextureUploads();
}
static bool updateUiDescriptorSet() {
if (g_vkDevice == VK_NULL_HANDLE || g_vkUiDescriptorSet == VK_NULL_HANDLE)
return false;
if (g_vkUiImageView == VK_NULL_HANDLE || g_vkUiSampler == VK_NULL_HANDLE)
return false;
VkDescriptorImageInfo imageInfo = {};
imageInfo.sampler = g_vkUiSampler;
imageInfo.imageView = g_vkUiImageView;
imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
VkWriteDescriptorSet write = {};
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write.dstSet = g_vkUiDescriptorSet;
write.dstBinding = 0;
write.dstArrayElement = 0;
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
write.descriptorCount = 1;
write.pImageInfo = &imageInfo;
vkUpdateDescriptorSets(g_vkDevice, 1, &write, 0, nullptr);
return true;
}
static bool recordPendingUiUpload(VkCommandBuffer cmd) {
if (!g_vkUiUpload.pending)
return true;
if (g_vkDevice == VK_NULL_HANDLE)
return false;
const size_t dataBytes = g_vkUiUpload.pixelsBGRA.size();
if (dataBytes == 0 || g_vkUiUpload.width == 0 || g_vkUiUpload.height == 0)
return true;
if (!createOrResizeUiImageResources(g_vkUiUpload.width, g_vkUiUpload.height))
return false;
if (!ensureUiStagingBuffer(dataBytes))
return false;
if (g_vkUiStagingMapped == nullptr)
return false;
VkResult result = VK_SUCCESS;
std::memcpy(g_vkUiStagingMapped, g_vkUiUpload.pixelsBGRA.data(), dataBytes);
if (!g_vkUiStagingHostCoherent) {
VkMappedMemoryRange range = {};
range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
range.memory = g_vkUiStagingMemory;
range.offset = 0;
range.size = VK_WHOLE_SIZE;
result = vkFlushMappedMemoryRanges(g_vkDevice, 1, &range);
if (result != VK_SUCCESS) {
debugVkResult("Failed to flush UI staging memory", result);
return false;
}
}
VkImageMemoryBarrier toTransfer = {};
toTransfer.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
toTransfer.oldLayout = g_vkUiImageLayout;
toTransfer.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
toTransfer.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
toTransfer.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
toTransfer.image = g_vkUiImage;
toTransfer.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
toTransfer.subresourceRange.baseMipLevel = 0;
toTransfer.subresourceRange.levelCount = 1;
toTransfer.subresourceRange.baseArrayLayer = 0;
toTransfer.subresourceRange.layerCount = 1;
VkPipelineStageFlags srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
if (g_vkUiImageLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
srcStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
toTransfer.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
} else if (g_vkUiImageLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
toTransfer.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
} else {
toTransfer.srcAccessMask = 0;
}
toTransfer.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
vkCmdPipelineBarrier(cmd, srcStage, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0,
nullptr, 0, nullptr, 1, &toTransfer);
VkBufferImageCopy copyRegion = {};
copyRegion.bufferOffset = 0;
copyRegion.bufferRowLength = 0;
copyRegion.bufferImageHeight = 0;
copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copyRegion.imageSubresource.mipLevel = 0;
copyRegion.imageSubresource.baseArrayLayer = 0;
copyRegion.imageSubresource.layerCount = 1;
copyRegion.imageOffset = {0, 0, 0};
copyRegion.imageExtent = {g_vkUiUpload.width, g_vkUiUpload.height, 1};
vkCmdCopyBufferToImage(cmd, g_vkUiStagingBuffer, g_vkUiImage,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copyRegion);
VkImageMemoryBarrier toRead = {};
toRead.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
toRead.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
toRead.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
toRead.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
toRead.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
toRead.image = g_vkUiImage;
toRead.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
toRead.subresourceRange.baseMipLevel = 0;
toRead.subresourceRange.levelCount = 1;
toRead.subresourceRange.baseArrayLayer = 0;
toRead.subresourceRange.layerCount = 1;
toRead.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
toRead.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, &toRead);
g_vkUiImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
g_vkUiImageReady = true;
g_vkUiUpload.pending = false;
updateUiDescriptorSet();
return true;
}
void C4JRender::Present() {
if (!g_vkInitialized)
return;
if (g_vkSwapImages.empty() || g_vkSwapImageLayouts.empty() ||
g_vkFramebuffers.empty() || g_vkCommandBuffer == VK_NULL_HANDLE ||
g_vkRenderPass == VK_NULL_HANDLE || g_vkTrianglePipeline == VK_NULL_HANDLE)
return;
uint32_t imageIndex = 0;
bool needRecreateSwapchain = false;
VkResult result =
vkAcquireNextImageKHR(g_vkDevice, g_vkSwapchain, UINT64_MAX,
g_vkImageAvailable, VK_NULL_HANDLE, &imageIndex);
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
needRecreateSwapchain = true;
} else if (result == VK_SUBOPTIMAL_KHR) {
needRecreateSwapchain = true;
} else if (result != VK_SUCCESS) {
debugVkResult("vkAcquireNextImageKHR failed", result);
return;
}
if (needRecreateSwapchain) {
if (g_vkWindow != NULL) {
int width = 0;
int height = 0;
if (getWindowClientSize(g_vkWindow, width, height)) {
Initialise(g_vkWindow, width, height);
}
}
return;
}
if (imageIndex >= g_vkSwapImages.size() ||
imageIndex >= g_vkSwapImageLayouts.size() ||
imageIndex >= g_vkFramebuffers.size()) {
debugVk("C4JRender_Vulkan: swapchain image index out of range.\n");
return;
}
uint64_t frameVertexCount = 0;
for (const VulkanQueuedDraw &draw : g_vkQueuedDraws) {
frameVertexCount += static_cast<uint64_t>(draw.vertexCount);
}
g_vkPerfDrawCount = static_cast<uint32_t>(g_vkQueuedDraws.size());
g_vkPerfVertexCount =
(frameVertexCount > 0xffffffffull) ? 0xffffffffu
: static_cast<uint32_t>(frameVertexCount);
g_vkPerfUploadBytes = (g_vkFrameVertexData.size() > 0xffffffffull)
? 0xffffffffu
: static_cast<uint32_t>(g_vkFrameVertexData.size());
if (g_vkPerfFreq.QuadPart == 0) {
QueryPerformanceFrequency(&g_vkPerfFreq);
}
LARGE_INTEGER nowCounter = {};
QueryPerformanceCounter(&nowCounter);
if (g_vkPerfLastPresent.QuadPart != 0 && g_vkPerfFreq.QuadPart > 0) {
const double dtSeconds =
static_cast<double>(nowCounter.QuadPart - g_vkPerfLastPresent.QuadPart) /
static_cast<double>(g_vkPerfFreq.QuadPart);
if (dtSeconds > 0.000001) {
const float instFps = static_cast<float>(1.0 / dtSeconds);
if (g_vkPerfFpsSmoothed <= 0.0f) {
g_vkPerfFpsSmoothed = instFps;
} else {
g_vkPerfFpsSmoothed =
g_vkPerfFpsSmoothed * 0.9f + instFps * 0.1f;
}
}
}
g_vkPerfLastPresent = nowCounter;
queueCornerOverlayText();
uint32_t uiQuadFirstVertex = 0;
uint32_t uiQuadVertexCount = 0;
if (g_vkUiImageReady || g_vkUiUpload.pending) {
appendUiCompositeFullscreenQuad(uiQuadFirstVertex, uiQuadVertexCount);
}
if (!g_vkFrameVertexData.empty()) {
if (!ensureDynamicVertexBuffer(g_vkFrameVertexData.size())) {
debugVk("C4JRender_Vulkan: Failed to ensure dynamic vertex buffer.\n");
return;
}
if (g_vkDynamicVertexMapped == nullptr) {
debugVk("C4JRender_Vulkan: Dynamic vertex buffer not mapped.\n");
return;
}
std::memcpy(g_vkDynamicVertexMapped, g_vkFrameVertexData.data(),
g_vkFrameVertexData.size());
if (!g_vkDynamicVertexHostCoherent) {
VkMappedMemoryRange range = {};
range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
range.memory = g_vkDynamicVertexMemory;
range.offset = 0;
range.size = VK_WHOLE_SIZE;
result = vkFlushMappedMemoryRanges(g_vkDevice, 1, &range);
if (result != VK_SUCCESS) {
debugVkResult("vkFlushMappedMemoryRanges failed", result);
return;
}
}
}
result = vkResetCommandBuffer(g_vkCommandBuffer, 0);
if (result != VK_SUCCESS) {
debugVkResult("vkResetCommandBuffer failed", result);
return;
}
VkCommandBufferBeginInfo beginInfo = {};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
result = vkBeginCommandBuffer(g_vkCommandBuffer, &beginInfo);
if (result != VK_SUCCESS) {
debugVkResult("vkBeginCommandBuffer failed", result);
return;
}
if (!recordPendingUiUpload(g_vkCommandBuffer)) {
debugVk("C4JRender_Vulkan: Failed to record pending UI upload.\n");
}
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
if (g_vkSwapImageLayouts[imageIndex] == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR)
srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
VkImageMemoryBarrier barrier = {};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.oldLayout = g_vkSwapImageLayouts[imageIndex];
barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = g_vkSwapImages[imageIndex];
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
vkCmdPipelineBarrier(g_vkCommandBuffer, srcStageMask,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0,
nullptr, 0, nullptr, 1, &barrier);
VkClearValue clearValues[2] = {};
clearValues[0].color.float32[0] = g_clearColour[0];
clearValues[0].color.float32[1] = g_clearColour[1];
clearValues[0].color.float32[2] = g_clearColour[2];
clearValues[0].color.float32[3] = g_clearColour[3];
clearValues[1].depthStencil.depth = 1.0f;
clearValues[1].depthStencil.stencil = 0;
VkRenderPassBeginInfo rpBegin = {};
rpBegin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
rpBegin.renderPass = g_vkRenderPass;
rpBegin.framebuffer = g_vkFramebuffers[imageIndex];
rpBegin.renderArea.offset = {0, 0};
rpBegin.renderArea.extent = g_vkSwapExtent;
rpBegin.clearValueCount = 2;
rpBegin.pClearValues = clearValues;
vkCmdBeginRenderPass(g_vkCommandBuffer, &rpBegin, VK_SUBPASS_CONTENTS_INLINE);
if (!g_vkQueuedDraws.empty()) {
VkBuffer vertexBuffers[] = {g_vkDynamicVertexBuffer};
VkDeviceSize offsets[] = {0};
vkCmdBindVertexBuffers(g_vkCommandBuffer, 0, 1, vertexBuffers, offsets);
VkPipeline boundPipeline = VK_NULL_HANDLE;
VkDescriptorSet boundDescriptorSet = VK_NULL_HANDLE;
float lastBlendConstants[4] = {-1.0f, -1.0f, -1.0f, -1.0f};
for (const VulkanQueuedDraw &draw : g_vkQueuedDraws) {
VkPipeline pipeline = getOrCreateTrianglePipeline(
draw.depthTestEnable, draw.depthWriteEnable, draw.depthCompareOp,
draw.blendEnable, draw.srcBlendFactor, draw.dstBlendFactor,
draw.colorWriteMask, draw.cullEnable, draw.cullClockwise);
if (pipeline == VK_NULL_HANDLE)
continue;
if (pipeline != boundPipeline) {
vkCmdBindPipeline(g_vkCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
pipeline);
boundPipeline = pipeline;
}
if (std::memcmp(lastBlendConstants, draw.blendConstants,
sizeof(lastBlendConstants)) != 0) {
vkCmdSetBlendConstants(g_vkCommandBuffer, draw.blendConstants);
std::memcpy(lastBlendConstants, draw.blendConstants,
sizeof(lastBlendConstants));
}
VkDescriptorSet descriptorSet = draw.descriptorSet;
if (descriptorSet == VK_NULL_HANDLE) {
descriptorSet = resolveTextureDescriptorSet(-1);
}
if (descriptorSet == VK_NULL_HANDLE) {
continue;
}
if (descriptorSet != boundDescriptorSet) {
vkCmdBindDescriptorSets(g_vkCommandBuffer,
VK_PIPELINE_BIND_POINT_GRAPHICS,
g_vkPipelineLayout, 0, 1, &descriptorSet, 0,
nullptr);
boundDescriptorSet = descriptorSet;
}
TrianglePushConstants push = {};
std::memcpy(push.mvp, draw.mvp, sizeof(push.mvp));
push.alphaState[0] = draw.alphaTestEnable ? 1.0f : 0.0f;
push.alphaState[1] = draw.alphaRef;
push.alphaState[2] = static_cast<float>(draw.alphaFunc);
push.alphaState[3] = 0.0f;
vkCmdPushConstants(g_vkCommandBuffer, g_vkPipelineLayout,
VK_SHADER_STAGE_VERTEX_BIT |
VK_SHADER_STAGE_FRAGMENT_BIT,
0, sizeof(push), &push);
vkCmdDraw(g_vkCommandBuffer, draw.vertexCount, 1, draw.firstVertex, 0);
}
}
if (uiQuadVertexCount > 0 && g_vkUiImageReady &&
g_vkUiPipeline != VK_NULL_HANDLE &&
g_vkUiPipelineLayout != VK_NULL_HANDLE &&
g_vkUiDescriptorSet != VK_NULL_HANDLE) {
VkBuffer vertexBuffers[] = {g_vkDynamicVertexBuffer};
VkDeviceSize offsets[] = {0};
float identity[16];
mat4_identity(identity);
vkCmdBindPipeline(g_vkCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
g_vkUiPipeline);
vkCmdBindVertexBuffers(g_vkCommandBuffer, 0, 1, vertexBuffers, offsets);
vkCmdBindDescriptorSets(g_vkCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
g_vkUiPipelineLayout, 0, 1, &g_vkUiDescriptorSet, 0,
nullptr);
vkCmdPushConstants(g_vkCommandBuffer, g_vkUiPipelineLayout,
VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(identity),
identity);
vkCmdDraw(g_vkCommandBuffer, uiQuadVertexCount, 1, uiQuadFirstVertex, 0);
}
vkCmdEndRenderPass(g_vkCommandBuffer);
result = vkEndCommandBuffer(g_vkCommandBuffer);
if (result != VK_SUCCESS) {
debugVkResult("vkEndCommandBuffer failed", result);
return;
}
VkPipelineStageFlags waitStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = &g_vkImageAvailable;
submitInfo.pWaitDstStageMask = &waitStage;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &g_vkCommandBuffer;
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = &g_vkRenderFinished;
result = vkResetFences(g_vkDevice, 1, &g_vkFence);
if (result != VK_SUCCESS) {
debugVkResult("vkResetFences failed", result);
g_vkInitialized = false;
return;
}
result = vkQueueSubmit(g_vkGraphicsQueue, 1, &submitInfo, g_vkFence);
if (result != VK_SUCCESS) {
debugVkResult("vkQueueSubmit failed", result);
g_vkInitialized = false;
return;
}
VkPresentInfoKHR presentInfo = {};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = &g_vkRenderFinished;
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = &g_vkSwapchain;
presentInfo.pImageIndices = &imageIndex;
result = vkQueuePresentKHR(g_vkGraphicsQueue, &presentInfo);
if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR && result != VK_ERROR_OUT_OF_DATE_KHR) {
debugVkResult("vkQueuePresentKHR failed", result);
return;
}
g_vkSwapImageLayouts[imageIndex] = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
if (g_vkFrameVertexData.size() > g_vkFrameVertexReserveBytes) {
g_vkFrameVertexReserveBytes = g_vkFrameVertexData.size();
}
if (g_vkQueuedDraws.size() > g_vkFrameDrawReserveCount) {
g_vkFrameDrawReserveCount = g_vkQueuedDraws.size();
}
if (result == VK_SUBOPTIMAL_KHR || result == VK_ERROR_OUT_OF_DATE_KHR) {
if (g_vkWindow != NULL) {
int width = 0;
int height = 0;
if (getWindowClientSize(g_vkWindow, width, height)) {
Initialise(g_vkWindow, width, height);
}
}
}
}
void C4JRender::DoScreenGrabOnNextPresent() {}
void C4JRender::Clear(int, C4JRect *) {}
void C4JRender::SetClearColour(const float c[4]) {
g_clearColour[0] = c[0];
g_clearColour[1] = c[1];
g_clearColour[2] = c[2];
g_clearColour[3] = c[3];
}
bool C4JRender::IsWidescreen() { return g_isWidescreen; }
bool C4JRender::IsHiDef() { return true; }
void C4JRender::CaptureThumbnail(ImageFileBuffer *) {}
void C4JRender::CaptureScreen(ImageFileBuffer *, XSOCIAL_PREVIEWIMAGE *) {}
void C4JRender::BeginConditionalSurvey(int) {}
void C4JRender::EndConditionalSurvey() {}
void C4JRender::BeginConditionalRendering(int) {}
void C4JRender::EndConditionalRendering() {}
// ============================================================================
// Draw calls
// ============================================================================
static inline void unpack565ToRGBA(uint16_t packed, uint8_t &r, uint8_t &g,
uint8_t &b, uint8_t &a) {
const uint8_t r5 = static_cast<uint8_t>((packed >> 11) & 0x1f);
const uint8_t g6 = static_cast<uint8_t>((packed >> 5) & 0x3f);
const uint8_t b5 = static_cast<uint8_t>(packed & 0x1f);
r = static_cast<uint8_t>((r5 * 255) / 31);
g = static_cast<uint8_t>((g6 * 255) / 63);
b = static_cast<uint8_t>((b5 * 255) / 31);
a = 255;
}
static inline uint8_t modulate8(uint8_t value, float mul) {
int out = static_cast<int>(static_cast<float>(value) * mul + 0.5f);
if (out < 0)
out = 0;
if (out > 255)
out = 255;
return static_cast<uint8_t>(out);
}
static inline float decodeLegacyNoMipmapU(float u) {
// old tesselator stores "no mip" as u+1.0f.
if (u > 1.0f && u < 2.0f) {
return u - 1.0f;
}
return u;
}
static inline bool vkColourMulIsIdentity() {
return (std::fabs(g_vkStateColour[0] - 1.0f) <= 0.0001f) &&
(std::fabs(g_vkStateColour[1] - 1.0f) <= 0.0001f) &&
(std::fabs(g_vkStateColour[2] - 1.0f) <= 0.0001f) &&
(std::fabs(g_vkStateColour[3] - 1.0f) <= 0.0001f);
}
static uint32_t expandVertexStreamToBootstrap(
std::vector<uint8_t> &out, C4JRender::ePrimitiveType primitiveType,
int count, C4JRender::eVertexType vType, const uint8_t *srcBytes,
bool applyColourMul, float colourMulR, float colourMulG, float colourMulB,
float colourMulA) {
const bool isStandardVertex =
(vType == C4JRender::VERTEX_TYPE_PF3_TF2_CB4_NB4_XW1 ||
vType == C4JRender::VERTEX_TYPE_PF3_TF2_CB4_NB4_XW1_TEXGEN ||
vType == C4JRender::VERTEX_TYPE_PF3_TF2_CB4_NB4_XW1_LIT);
const bool isCompressedVertex = (vType == C4JRender::VERTEX_TYPE_COMPRESSED);
if (!isStandardVertex && !isCompressedVertex)
return 0;
if (primitiveType != C4JRender::PRIMITIVE_TYPE_TRIANGLE_LIST &&
primitiveType != C4JRender::PRIMITIVE_TYPE_QUAD_LIST) {
return 0;
}
if (count <= 0 || srcBytes == nullptr)
return 0;
const uint32_t outVertexCount =
(primitiveType == C4JRender::PRIMITIVE_TYPE_TRIANGLE_LIST)
? static_cast<uint32_t>(count)
: static_cast<uint32_t>((count / 4) * 6);
if (outVertexCount == 0)
return 0;
const size_t oldSize = out.size();
out.resize(oldSize +
static_cast<size_t>(outVertexCount) * kVertexStridePF3TF2CB4NB4XW1);
uint8_t *dst = out.data() + oldSize;
const size_t sourceStride =
isCompressedVertex ? 16 : kVertexStridePF3TF2CB4NB4XW1;
auto emitExpandedVertex = [&](int srcVertexIndex) {
const uint8_t *src =
srcBytes + static_cast<size_t>(srcVertexIndex) * sourceStride;
if (isStandardVertex) {
std::memcpy(dst, src, kVertexStridePF3TF2CB4NB4XW1);
float srcU = 0.0f;
std::memcpy(&srcU, src + 12, sizeof(float));
srcU = decodeLegacyNoMipmapU(srcU);
std::memcpy(dst + 12, &srcU, sizeof(float));
// Input color bytes are ABGR in little-endian memory. Convert to RGBA.
const uint8_t srcA = src[20];
const uint8_t srcB = src[21];
const uint8_t srcG = src[22];
const uint8_t srcR = src[23];
dst[20] = srcR;
dst[21] = srcG;
dst[22] = srcB;
dst[23] = srcA;
if (applyColourMul) {
dst[20] = modulate8(dst[20], colourMulR);
dst[21] = modulate8(dst[21], colourMulG);
dst[22] = modulate8(dst[22], colourMulB);
dst[23] = modulate8(dst[23], colourMulA);
}
dst += kVertexStridePF3TF2CB4NB4XW1;
return;
}
const int16_t *s = reinterpret_cast<const int16_t *>(src);
const float x = static_cast<float>(s[0]) / 1024.0f;
const float y = static_cast<float>(s[1]) / 1024.0f;
const float z = static_cast<float>(s[2]) / 1024.0f;
const uint16_t encodedColour = static_cast<uint16_t>(s[3]);
const uint16_t packed565 = static_cast<uint16_t>(encodedColour + 32768u);
const float u = static_cast<float>(s[4]) / 8192.0f;
const float v = static_cast<float>(s[5]) / 8192.0f;
uint8_t r = 255, g = 255, b = 255, a = 255;
unpack565ToRGBA(packed565, r, g, b, a);
if (applyColourMul) {
r = modulate8(r, colourMulR);
g = modulate8(g, colourMulG);
b = modulate8(b, colourMulB);
a = modulate8(a, colourMulA);
}
BootstrapVertex outVertex = {x, y, z, u, v, r, g, b, a, 0, 0, 0, 0, 0};
std::memcpy(dst, &outVertex, sizeof(outVertex));
dst += sizeof(outVertex);
};
if (primitiveType == C4JRender::PRIMITIVE_TYPE_TRIANGLE_LIST) {
for (int i = 0; i < count; ++i) {
emitExpandedVertex(i);
}
} else {
const int quadCount = count / 4;
for (int q = 0; q < quadCount; ++q) {
const int base = q * 4;
emitExpandedVertex(base + 0);
emitExpandedVertex(base + 1);
emitExpandedVertex(base + 2);
emitExpandedVertex(base + 2);
emitExpandedVertex(base + 3);
emitExpandedVertex(base + 0);
}
}
return outVertexCount;
}
static void queuePreparedExpandedDrawWithMvp(const uint8_t *vertexBytes,
uint32_t vertexCount,
const float *mvp) {
if (!g_vkInitialized || vertexBytes == nullptr || vertexCount == 0)
return;
const size_t firstVertex =
g_vkFrameVertexData.size() / kVertexStridePF3TF2CB4NB4XW1;
const size_t oldSize = g_vkFrameVertexData.size();
const size_t addBytes =
static_cast<size_t>(vertexCount) * kVertexStridePF3TF2CB4NB4XW1;
g_vkFrameVertexData.resize(oldSize + addBytes);
std::memcpy(g_vkFrameVertexData.data() + oldSize, vertexBytes, addBytes);
VulkanQueuedDraw draw = {};
draw.firstVertex = static_cast<uint32_t>(firstVertex);
draw.vertexCount = vertexCount;
draw.depthTestEnable = g_vkStateDepthTestEnable;
draw.depthWriteEnable = g_vkStateDepthWriteEnable;
draw.depthCompareOp = g_vkStateDepthCompareOp;
draw.blendEnable = g_vkStateBlendEnable;
draw.srcBlendFactor = g_vkStateSrcBlendFactor;
draw.dstBlendFactor = g_vkStateDstBlendFactor;
draw.colorWriteMask = g_vkStateColorWriteMask;
draw.cullEnable = g_vkStateCullEnable;
draw.cullClockwise = g_vkStateCullClockwise;
std::memcpy(draw.blendConstants, g_vkStateBlendConstants,
sizeof(draw.blendConstants));
draw.descriptorSet = resolveTextureDescriptorSet(g_vkStateTextureId);
draw.alphaTestEnable = g_vkStateAlphaTestEnable;
draw.alphaFunc = g_vkStateAlphaFunc;
draw.alphaRef = g_vkStateAlphaRef;
if (mvp != nullptr) {
std::memcpy(draw.mvp, mvp, sizeof(draw.mvp));
} else {
mat4_identity(draw.mvp);
}
g_vkQueuedDraws.push_back(draw);
}
void C4JRender::DrawVertices(ePrimitiveType primitiveType, int count,
void *dataIn, eVertexType vType,
ePixelShaderType psType) {
if (!g_vkInitialized || count <= 0 || dataIn == nullptr)
return;
if (primitiveType != PRIMITIVE_TYPE_TRIANGLE_LIST &&
primitiveType != PRIMITIVE_TYPE_QUAD_LIST)
return;
const bool isStandardVertex =
(vType == VERTEX_TYPE_PF3_TF2_CB4_NB4_XW1 ||
vType == VERTEX_TYPE_PF3_TF2_CB4_NB4_XW1_TEXGEN ||
vType == VERTEX_TYPE_PF3_TF2_CB4_NB4_XW1_LIT);
const bool isCompressedVertex = (vType == VERTEX_TYPE_COMPRESSED);
if (!isStandardVertex && !isCompressedVertex)
return;
if (g_vkIsRecordingCommandList && g_vkRecordingCommandListIndex >= 0) {
RecordedDrawCall call = {};
call.primitiveType = primitiveType;
call.count = count;
call.vType = vType;
call.psType = psType;
call.preparedVertexCount = 0;
call.hasLocalModelMatrix = true;
const float *recordModelView = MatrixGet(GL_MODELVIEW_MATRIX);
if (recordModelView != nullptr && g_vkRecordingBaseModelValid) {
// Display-list semantics: matrix ops inside the list are relative to
// matrix state at CBuffStart, not absolute compile-time camera state.
mat4_multiply(call.localModelMatrix, g_vkRecordingBaseModelInv,
recordModelView);
} else {
mat4_identity(call.localModelMatrix);
}
call.useCapturedState = g_vkRecordingHasStateChanges;
call.depthTestEnable = g_vkStateDepthTestEnable;
call.depthWriteEnable = g_vkStateDepthWriteEnable;
call.depthCompareOp = g_vkStateDepthCompareOp;
call.blendEnable = g_vkStateBlendEnable;
call.srcBlendFactor = g_vkStateSrcBlendFactor;
call.dstBlendFactor = g_vkStateDstBlendFactor;
call.colorWriteMask = g_vkStateColorWriteMask;
call.cullEnable = g_vkStateCullEnable;
call.cullClockwise = g_vkStateCullClockwise;
std::memcpy(call.blendConstants, g_vkStateBlendConstants,
sizeof(call.blendConstants));
// bug we hit: replaying compile-time textureId broke chunk textures.
call.captureTextureState = g_vkRecordingHasTextureStateChanges;
call.textureId = g_vkStateTextureId;
call.captureAlphaState = g_vkRecordingHasAlphaStateChanges;
call.alphaTestEnable = g_vkStateAlphaTestEnable;
call.alphaFunc = g_vkStateAlphaFunc;
call.alphaRef = g_vkStateAlphaRef;
const size_t sourceStride =
isCompressedVertex ? 16 : kVertexStridePF3TF2CB4NB4XW1;
call.vertexData.resize(static_cast<size_t>(count) * sourceStride);
std::memcpy(call.vertexData.data(), dataIn, call.vertexData.size());
call.preparedVertexCount = expandVertexStreamToBootstrap(
call.preparedVertexData, primitiveType, count, vType,
call.vertexData.data(), false, 1.0f, 1.0f, 1.0f, 1.0f);
g_vkRecordingScratch.push_back(std::move(call));
return;
}
const uint8_t *srcBytes = static_cast<const uint8_t *>(dataIn);
const size_t firstVertex =
g_vkFrameVertexData.size() / kVertexStridePF3TF2CB4NB4XW1;
const bool applyColourMul = !vkColourMulIsIdentity();
uint32_t outVertexCount = expandVertexStreamToBootstrap(
g_vkFrameVertexData, primitiveType, count, vType, srcBytes, applyColourMul,
g_vkStateColour[0], g_vkStateColour[1], g_vkStateColour[2],
g_vkStateColour[3]);
if (outVertexCount == 0)
return;
VulkanQueuedDraw draw = {};
draw.firstVertex = static_cast<uint32_t>(firstVertex);
draw.vertexCount = outVertexCount;
draw.depthTestEnable = g_vkStateDepthTestEnable;
draw.depthWriteEnable = g_vkStateDepthWriteEnable;
draw.depthCompareOp = g_vkStateDepthCompareOp;
draw.blendEnable = g_vkStateBlendEnable;
draw.srcBlendFactor = g_vkStateSrcBlendFactor;
draw.dstBlendFactor = g_vkStateDstBlendFactor;
draw.colorWriteMask = g_vkStateColorWriteMask;
draw.cullEnable = g_vkStateCullEnable;
draw.cullClockwise = g_vkStateCullClockwise;
std::memcpy(draw.blendConstants, g_vkStateBlendConstants,
sizeof(draw.blendConstants));
draw.descriptorSet = resolveTextureDescriptorSet(g_vkStateTextureId);
draw.alphaTestEnable = g_vkStateAlphaTestEnable;
draw.alphaFunc = g_vkStateAlphaFunc;
draw.alphaRef = g_vkStateAlphaRef;
const float *modelView = MatrixGet(GL_MODELVIEW_MATRIX);
const float *projection = MatrixGet(GL_PROJECTION_MATRIX);
if (modelView != nullptr && projection != nullptr) {
mat4_multiply(draw.mvp, projection, modelView);
} else {
mat4_identity(draw.mvp);
}
g_vkQueuedDraws.push_back(draw);
}
void C4JRender::DrawVertexBuffer(ePrimitiveType, int, void *, eVertexType,
ePixelShaderType) {}
// ============================================================================
// Command buffers
// ============================================================================
void C4JRender::CBuffLockStaticCreations() {}
int C4JRender::CBuffCreate(int count) {
if (count <= 0)
count = 1;
std::lock_guard<std::mutex> commandListsLock(g_vkCommandListsMutex);
int first = g_nextCommandBufferId;
g_nextCommandBufferId += count;
for (int i = 0; i < count; ++i) {
g_vkCommandLists[first + i] = std::make_shared<std::vector<RecordedDrawCall>>();
}
return first;
}
void C4JRender::CBuffDelete(int first, int count) {
if (count <= 0)
count = 1;
{
std::lock_guard<std::mutex> commandListsLock(g_vkCommandListsMutex);
for (int i = 0; i < count; ++i) {
g_vkCommandLists.erase(first + i);
}
}
if (g_vkIsRecordingCommandList &&
g_vkRecordingCommandListIndex >= first &&
g_vkRecordingCommandListIndex < (first + count)) {
g_vkIsRecordingCommandList = false;
g_vkRecordingCommandListIndex = -1;
g_vkRecordingScratch.clear();
g_vkRecordingHasStateChanges = false;
g_vkRecordingHasTextureStateChanges = false;
g_vkRecordingHasAlphaStateChanges = false;
g_vkRecordingBaseModelValid = false;
}
}
void C4JRender::CBuffStart(int index, bool full) {
(void)full;
ensureThreadLocalMatrixStacksInitialised();
g_vkIsRecordingCommandList = true;
g_vkRecordingCommandListIndex = index;
{
std::lock_guard<std::mutex> commandListsLock(g_vkCommandListsMutex);
if (g_vkCommandLists.find(index) == g_vkCommandLists.end())
g_vkCommandLists[index] = std::make_shared<std::vector<RecordedDrawCall>>();
}
g_vkRecordingScratch.clear();
g_vkRecordingHasStateChanges = false;
g_vkRecordingHasTextureStateChanges = false;
g_vkRecordingHasAlphaStateChanges = false;
const float *baseModel = MatrixGet(GL_MODELVIEW_MATRIX);
if (baseModel != nullptr) {
g_vkRecordingBaseModelValid =
mat4_inverse(g_vkRecordingBaseModelInv, baseModel);
} else {
g_vkRecordingBaseModelValid = false;
}
if (!g_vkRecordingBaseModelValid) {
mat4_identity(g_vkRecordingBaseModelInv);
g_vkRecordingBaseModelValid = true;
}
}
void C4JRender::CBuffClear(int index) {
std::lock_guard<std::mutex> commandListsLock(g_vkCommandListsMutex);
g_vkCommandLists[index] = std::make_shared<std::vector<RecordedDrawCall>>();
}
int C4JRender::CBuffSize(int index) {
// old renderers used this as allocator pressure.
// for vulkan, returning big values here made chunk rebuild stall.
if (index == -1) {
return 0;
}
size_t bytes = 0;
std::lock_guard<std::mutex> commandListsLock(g_vkCommandListsMutex);
auto it = g_vkCommandLists.find(index);
if (it != g_vkCommandLists.end() && it->second) {
for (const auto &call : *it->second) {
bytes += call.vertexData.size();
}
}
return static_cast<int>(bytes);
}
void C4JRender::CBuffEnd() {
if (g_vkIsRecordingCommandList && g_vkRecordingCommandListIndex >= 0) {
std::lock_guard<std::mutex> commandListsLock(g_vkCommandListsMutex);
g_vkCommandLists[g_vkRecordingCommandListIndex] =
std::make_shared<std::vector<RecordedDrawCall>>(
std::move(g_vkRecordingScratch));
g_vkRecordingScratch.clear();
} else {
g_vkRecordingScratch.clear();
}
g_vkIsRecordingCommandList = false;
g_vkRecordingCommandListIndex = -1;
g_vkRecordingHasStateChanges = false;
g_vkRecordingHasTextureStateChanges = false;
g_vkRecordingHasAlphaStateChanges = false;
g_vkRecordingBaseModelValid = false;
}
bool C4JRender::CBuffCall(int index, bool) {
std::shared_ptr<std::vector<RecordedDrawCall>> calls;
{
std::lock_guard<std::mutex> commandListsLock(g_vkCommandListsMutex);
auto it = g_vkCommandLists.find(index);
if (it == g_vkCommandLists.end())
return false;
calls = it->second;
}
if (!calls)
return false;
ensureThreadLocalMatrixStacksInitialised();
float callSiteModelView[16];
const float *callSiteModelViewPtr = MatrixGet(GL_MODELVIEW_MATRIX);
if (callSiteModelViewPtr != nullptr) {
std::memcpy(callSiteModelView, callSiteModelViewPtr,
sizeof(callSiteModelView));
} else {
mat4_identity(callSiteModelView);
}
float callSiteProjection[16];
const float *callSiteProjectionPtr = MatrixGet(GL_PROJECTION_MATRIX);
if (callSiteProjectionPtr != nullptr) {
std::memcpy(callSiteProjection, callSiteProjectionPtr,
sizeof(callSiteProjection));
} else {
mat4_identity(callSiteProjection);
}
const bool wasRecording = g_vkIsRecordingCommandList;
const int oldRecordingIndex = g_vkRecordingCommandListIndex;
const bool oldDepthTestEnable = g_vkStateDepthTestEnable;
const bool oldDepthWriteEnable = g_vkStateDepthWriteEnable;
const VkCompareOp oldDepthCompareOp = g_vkStateDepthCompareOp;
const bool oldBlendEnable = g_vkStateBlendEnable;
const VkBlendFactor oldSrcBlendFactor = g_vkStateSrcBlendFactor;
const VkBlendFactor oldDstBlendFactor = g_vkStateDstBlendFactor;
const VkColorComponentFlags oldColorWriteMask = g_vkStateColorWriteMask;
const bool oldCullEnable = g_vkStateCullEnable;
const bool oldCullClockwise = g_vkStateCullClockwise;
const int oldTextureId = g_vkStateTextureId;
const bool oldAlphaTestEnable = g_vkStateAlphaTestEnable;
const int oldAlphaFunc = g_vkStateAlphaFunc;
const float oldAlphaRef = g_vkStateAlphaRef;
float oldBlendConstants[4] = {g_vkStateBlendConstants[0],
g_vkStateBlendConstants[1],
g_vkStateBlendConstants[2],
g_vkStateBlendConstants[3]};
g_vkIsRecordingCommandList = false;
g_vkRecordingCommandListIndex = -1;
for (const RecordedDrawCall &call : *calls) {
if (call.vertexData.empty() || call.count <= 0)
continue;
if (call.useCapturedState) {
g_vkStateDepthTestEnable = call.depthTestEnable;
g_vkStateDepthWriteEnable = call.depthWriteEnable;
g_vkStateDepthCompareOp = call.depthCompareOp;
g_vkStateBlendEnable = call.blendEnable;
g_vkStateSrcBlendFactor = call.srcBlendFactor;
g_vkStateDstBlendFactor = call.dstBlendFactor;
g_vkStateColorWriteMask = call.colorWriteMask;
g_vkStateCullEnable = call.cullEnable;
g_vkStateCullClockwise = call.cullClockwise;
std::memcpy(g_vkStateBlendConstants, call.blendConstants,
sizeof(call.blendConstants));
}
if (call.captureTextureState) {
// only change texture state if this draw recorded a TextureBind.
g_vkStateTextureId = call.textureId;
}
if (call.captureAlphaState) {
g_vkStateAlphaTestEnable = call.alphaTestEnable;
g_vkStateAlphaFunc = call.alphaFunc;
g_vkStateAlphaRef = call.alphaRef;
}
const bool canUsePrepared =
vkColourMulIsIdentity() && (call.preparedVertexCount > 0) &&
!call.preparedVertexData.empty();
if (canUsePrepared) {
float drawMvp[16];
if (call.hasLocalModelMatrix) {
float combinedModelView[16];
mat4_multiply(combinedModelView, callSiteModelView, call.localModelMatrix);
mat4_multiply(drawMvp, callSiteProjection, combinedModelView);
} else {
mat4_multiply(drawMvp, callSiteProjection, callSiteModelView);
}
queuePreparedExpandedDrawWithMvp(call.preparedVertexData.data(),
call.preparedVertexCount, drawMvp);
} else {
if (call.hasLocalModelMatrix) {
float combinedModelView[16];
mat4_multiply(combinedModelView, callSiteModelView, call.localModelMatrix);
std::memcpy(g_matStacks[GL_MODELVIEW].stack[g_matStacks[GL_MODELVIEW].top],
combinedModelView, sizeof(combinedModelView));
}
DrawVertices(call.primitiveType, call.count,
const_cast<uint8_t *>(call.vertexData.data()), call.vType,
call.psType);
if (call.hasLocalModelMatrix) {
std::memcpy(g_matStacks[GL_MODELVIEW].stack[g_matStacks[GL_MODELVIEW].top],
callSiteModelView, sizeof(callSiteModelView));
}
}
}
g_vkStateDepthTestEnable = oldDepthTestEnable;
g_vkStateDepthWriteEnable = oldDepthWriteEnable;
g_vkStateDepthCompareOp = oldDepthCompareOp;
g_vkStateBlendEnable = oldBlendEnable;
g_vkStateSrcBlendFactor = oldSrcBlendFactor;
g_vkStateDstBlendFactor = oldDstBlendFactor;
g_vkStateColorWriteMask = oldColorWriteMask;
g_vkStateCullEnable = oldCullEnable;
g_vkStateCullClockwise = oldCullClockwise;
g_vkStateTextureId = oldTextureId;
g_vkStateAlphaTestEnable = oldAlphaTestEnable;
g_vkStateAlphaFunc = oldAlphaFunc;
g_vkStateAlphaRef = oldAlphaRef;
std::memcpy(g_vkStateBlendConstants, oldBlendConstants,
sizeof(oldBlendConstants));
g_vkIsRecordingCommandList = wasRecording;
g_vkRecordingCommandListIndex = oldRecordingIndex;
return true;
}
void C4JRender::CBuffTick() {}
void C4JRender::CBuffDeferredModeStart() {}
void C4JRender::CBuffDeferredModeEnd() {}
// ============================================================================
// Textures
// ============================================================================
int C4JRender::TextureCreate() {
const int id = g_nextTextureId++;
VulkanTexture tex = {};
tex.id = id;
tex.width = 0;
tex.height = 0;
tex.mipLevels = 1;
tex.image = VK_NULL_HANDLE;
tex.memory = VK_NULL_HANDLE;
tex.imageView = VK_NULL_HANDLE;
tex.sampler = VK_NULL_HANDLE;
tex.descriptorSet = VK_NULL_HANDLE;
tex.imageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
tex.minFilter = GL_NEAREST;
tex.magFilter = GL_NEAREST;
tex.wrapS = GL_REPEAT;
tex.wrapT = GL_REPEAT;
tex.requestedMipLevels = 1;
tex.pendingUpload = false;
tex.levelData.clear();
g_vkTextures[id] = tex;
return id;
}
void C4JRender::TextureFree(int idx) {
if (idx <= 0)
return;
auto it = g_vkTextures.find(idx);
if (it == g_vkTextures.end())
return;
destroyTextureGpuResources(it->second);
g_vkTextures.erase(it);
if (g_vkStateTextureId == idx)
g_vkStateTextureId = -1;
if (g_vkStateVertexTextureId == idx)
g_vkStateVertexTextureId = -1;
}
void C4JRender::TextureBind(int idx) {
g_vkStateTextureId = idx;
// record that this bind happened, so replay uses it.
if (g_vkIsRecordingCommandList)
g_vkRecordingHasTextureStateChanges = true;
if (idx < 0)
return;
auto it = g_vkTextures.find(idx);
if (it == g_vkTextures.end())
return;
if (it->second.pendingUpload) {
ensureTextureUploadedFromCache(it->second);
} else if (it->second.image != VK_NULL_HANDLE &&
it->second.descriptorSet == VK_NULL_HANDLE &&
hasTextureUploadContext()) {
updateTextureDescriptorSet(it->second);
}
}
void C4JRender::TextureBindVertex(int idx) { g_vkStateVertexTextureId = idx; }
void C4JRender::TextureSetTextureLevels(int levels) {
if (levels < 1)
levels = 1;
if (levels > 16)
levels = 16;
g_vkPendingTextureLevels = levels;
auto it = g_vkTextures.find(g_vkStateTextureId);
if (it != g_vkTextures.end()) {
it->second.requestedMipLevels = static_cast<uint32_t>(levels);
}
}
int C4JRender::TextureGetTextureLevels() {
auto it = g_vkTextures.find(g_vkStateTextureId);
if (it != g_vkTextures.end() && it->second.mipLevels > 0) {
return static_cast<int>(it->second.mipLevels);
}
return g_vkPendingTextureLevels;
}
void C4JRender::TextureData(int width, int height, void *data, int level,
eTextureFormat format) {
(void)format;
if (g_vkStateTextureId < 0 || width <= 0 || height <= 0 || data == nullptr ||
level < 0) {
return;
}
auto it = g_vkTextures.find(g_vkStateTextureId);
if (it == g_vkTextures.end()) {
return;
}
VulkanTexture &tex = it->second;
const uint32_t uploadLevel = static_cast<uint32_t>(level);
cacheTextureLevelPixels(tex, uploadLevel, static_cast<uint32_t>(width),
static_cast<uint32_t>(height), data);
uint32_t requestedLevels =
static_cast<uint32_t>((g_vkPendingTextureLevels > 0) ? g_vkPendingTextureLevels
: 1);
if (requestedLevels <= uploadLevel)
requestedLevels = uploadLevel + 1;
tex.requestedMipLevels = requestedLevels;
if (!hasTextureUploadContext()) {
return;
}
if (ensureTextureUploadedFromCache(tex)) {
return;
}
uint32_t targetLevels =
static_cast<uint32_t>((g_vkPendingTextureLevels > 0) ? g_vkPendingTextureLevels
: 1);
if (targetLevels <= uploadLevel)
targetLevels = uploadLevel + 1;
uint32_t baseWidth = static_cast<uint32_t>(width);
uint32_t baseHeight = static_cast<uint32_t>(height);
if (uploadLevel > 0) {
baseWidth = baseWidth << uploadLevel;
baseHeight = baseHeight << uploadLevel;
}
const bool needsRecreate =
(tex.image == VK_NULL_HANDLE) || (tex.width != baseWidth) ||
(tex.height != baseHeight) || (tex.mipLevels != targetLevels);
if (needsRecreate) {
if (!createTextureImageAndView(tex, baseWidth, baseHeight, targetLevels))
return;
}
if (uploadTextureRegionImmediate(tex, uploadLevel, 0, 0,
static_cast<uint32_t>(width),
static_cast<uint32_t>(height), data)) {
tex.pendingUpload = false;
}
}
void C4JRender::TextureDataUpdate(int xoffset, int yoffset, int width, int height,
void *data, int level) {
if (g_vkStateTextureId < 0 || width <= 0 || height <= 0 || data == nullptr ||
level < 0 || xoffset < 0 || yoffset < 0) {
return;
}
auto it = g_vkTextures.find(g_vkStateTextureId);
if (it == g_vkTextures.end()) {
return;
}
VulkanTexture &tex = it->second;
const uint32_t uploadLevel = static_cast<uint32_t>(level);
const uint32_t uWidth = static_cast<uint32_t>(width);
const uint32_t uHeight = static_cast<uint32_t>(height);
const uint32_t uX = static_cast<uint32_t>(xoffset);
const uint32_t uY = static_cast<uint32_t>(yoffset);
bool cachedPatch = patchTextureLevelPixels(tex, uploadLevel, uX, uY, uWidth,
uHeight, data);
if (!cachedPatch && xoffset == 0 && yoffset == 0) {
cacheTextureLevelPixels(tex, uploadLevel, uWidth, uHeight, data);
cachedPatch = true;
}
if (!hasTextureUploadContext()) {
if (!cachedPatch) {
TextureData(width, height, data, level, TEXTURE_FORMAT_RxGyBzAw);
}
return;
}
if (cachedPatch && ensureTextureUploadedFromCache(tex)) {
return;
}
if (tex.image == VK_NULL_HANDLE || uploadLevel >= tex.mipLevels) {
TextureData(width, height, data, level, TEXTURE_FORMAT_RxGyBzAw);
return;
}
uploadTextureRegionImmediate(
tex, uploadLevel, uX, uY, uWidth, uHeight, data);
tex.pendingUpload = false;
}
void C4JRender::TextureSetParam(int param, int value) {
auto it = g_vkTextures.find(g_vkStateTextureId);
if (it == g_vkTextures.end())
return;
VulkanTexture &tex = it->second;
switch (param) {
case GL_TEXTURE_MIN_FILTER:
tex.minFilter = value;
break;
case GL_TEXTURE_MAG_FILTER:
tex.magFilter = value;
break;
case GL_TEXTURE_WRAP_S:
tex.wrapS = value;
break;
case GL_TEXTURE_WRAP_T:
tex.wrapT = value;
break;
default:
return;
}
if (tex.image != VK_NULL_HANDLE) {
recreateTextureSampler(tex);
}
}
void C4JRender::TextureDynamicUpdateStart() {}
void C4JRender::TextureDynamicUpdateEnd() {}
HRESULT C4JRender::LoadTextureData(const char *szFilename,
D3DXIMAGE_INFO *pSrcInfo, int **ppDataOut) {
if (!szFilename || !pSrcInfo || !ppDataOut)
return E_INVALIDARG;
*ppDataOut = nullptr;
pSrcInfo->Width = 0;
pSrcInfo->Height = 0;
HRESULT coHr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
const bool shouldUninit = SUCCEEDED(coHr);
if (coHr == RPC_E_CHANGED_MODE)
coHr = S_OK;
if (FAILED(coHr))
return coHr;
std::wstring wpath = toWidePath(szFilename);
if (wpath.empty()) {
if (shouldUninit)
CoUninitialize();
return E_INVALIDARG;
}
IWICImagingFactory *factory = nullptr;
IWICBitmapDecoder *decoder = nullptr;
IWICBitmapFrameDecode *frame = nullptr;
HRESULT hr = CoCreateInstance(CLSID_WICImagingFactory, nullptr,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&factory));
if (SUCCEEDED(hr)) {
hr = factory->CreateDecoderFromFilename(wpath.c_str(), nullptr,
GENERIC_READ,
WICDecodeMetadataCacheOnLoad,
&decoder);
}
if (SUCCEEDED(hr)) {
hr = decoder->GetFrame(0, &frame);
}
if (SUCCEEDED(hr)) {
hr = decodeFrameToArgb(frame, pSrcInfo, ppDataOut);
}
SafeReleaseCOM(frame);
SafeReleaseCOM(decoder);
SafeReleaseCOM(factory);
if (shouldUninit)
CoUninitialize();
return hr;
}
HRESULT C4JRender::LoadTextureData(BYTE *pbData, DWORD dwBytes,
D3DXIMAGE_INFO *pSrcInfo, int **ppDataOut) {
if (!pbData || dwBytes == 0 || !pSrcInfo || !ppDataOut)
return E_INVALIDARG;
*ppDataOut = nullptr;
pSrcInfo->Width = 0;
pSrcInfo->Height = 0;
HRESULT coHr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
const bool shouldUninit = SUCCEEDED(coHr);
if (coHr == RPC_E_CHANGED_MODE)
coHr = S_OK;
if (FAILED(coHr))
return coHr;
IWICImagingFactory *factory = nullptr;
IWICStream *stream = nullptr;
IWICBitmapDecoder *decoder = nullptr;
IWICBitmapFrameDecode *frame = nullptr;
HRESULT hr = CoCreateInstance(CLSID_WICImagingFactory, nullptr,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&factory));
if (SUCCEEDED(hr)) {
hr = factory->CreateStream(&stream);
}
if (SUCCEEDED(hr)) {
hr = stream->InitializeFromMemory(pbData, dwBytes);
}
if (SUCCEEDED(hr)) {
hr = factory->CreateDecoderFromStream(stream, nullptr,
WICDecodeMetadataCacheOnLoad,
&decoder);
}
if (SUCCEEDED(hr)) {
hr = decoder->GetFrame(0, &frame);
}
if (SUCCEEDED(hr)) {
hr = decodeFrameToArgb(frame, pSrcInfo, ppDataOut);
}
SafeReleaseCOM(frame);
SafeReleaseCOM(decoder);
SafeReleaseCOM(stream);
SafeReleaseCOM(factory);
if (shouldUninit)
CoUninitialize();
return hr;
}
HRESULT C4JRender::SaveTextureData(const char *, D3DXIMAGE_INFO *, int *) {
return E_NOTIMPL;
}
HRESULT C4JRender::SaveTextureDataToMemory(void *, int, int *, int, int,
int *) {
return E_NOTIMPL;
}
void C4JRender::TextureGetStats() {}
void *C4JRender::TextureGetTexture(int) { return nullptr; }
// ============================================================================
// State control
// ============================================================================
void C4JRender::StateSetColour(float r, float g, float b, float a) {
auto clamp01 = [](float value) {
if (value < 0.0f)
return 0.0f;
if (value > 1.0f)
return 1.0f;
return value;
};
g_vkStateColour[0] = clamp01(r);
g_vkStateColour[1] = clamp01(g);
g_vkStateColour[2] = clamp01(b);
g_vkStateColour[3] = clamp01(a);
}
void C4JRender::StateSetDepthMask(bool enable) {
g_vkStateDepthWriteEnable = enable;
if (g_vkIsRecordingCommandList)
g_vkRecordingHasStateChanges = true;
}
void C4JRender::StateSetBlendEnable(bool enable) {
g_vkStateBlendEnable = enable;
if (g_vkIsRecordingCommandList)
g_vkRecordingHasStateChanges = true;
}
void C4JRender::StateSetBlendFunc(int src, int dst) {
g_vkStateSrcBlendFactor = mapBlendFactorToVk(src);
g_vkStateDstBlendFactor = mapBlendFactorToVk(dst);
if (g_vkIsRecordingCommandList)
g_vkRecordingHasStateChanges = true;
}
void C4JRender::StateSetBlendFactor(unsigned int colour) {
const float a = static_cast<float>((colour >> 24) & 0xff) / 255.0f;
const float r = static_cast<float>((colour >> 16) & 0xff) / 255.0f;
const float g = static_cast<float>((colour >> 8) & 0xff) / 255.0f;
const float b = static_cast<float>(colour & 0xff) / 255.0f;
g_vkStateBlendConstants[0] = r;
g_vkStateBlendConstants[1] = g;
g_vkStateBlendConstants[2] = b;
g_vkStateBlendConstants[3] = a;
if (g_vkIsRecordingCommandList)
g_vkRecordingHasStateChanges = true;
}
void C4JRender::StateSetAlphaFunc(int func, float param) {
g_vkStateAlphaFunc = func;
g_vkStateAlphaRef = param;
if (g_vkIsRecordingCommandList)
g_vkRecordingHasAlphaStateChanges = true;
}
void C4JRender::StateSetDepthFunc(int func) {
g_vkStateDepthCompareOp = mapDepthFuncToVk(func);
if (g_vkIsRecordingCommandList)
g_vkRecordingHasStateChanges = true;
}
void C4JRender::StateSetFaceCull(bool enable) {
g_vkStateCullEnable = enable;
if (g_vkIsRecordingCommandList)
g_vkRecordingHasStateChanges = true;
}
void C4JRender::StateSetFaceCullCW(bool enable) {
g_vkStateCullClockwise = enable;
if (g_vkIsRecordingCommandList)
g_vkRecordingHasStateChanges = true;
}
void C4JRender::StateSetLineWidth(float) {}
void C4JRender::StateSetWriteEnable(bool red, bool green, bool blue, bool alpha) {
VkColorComponentFlags mask = 0;
if (red)
mask |= VK_COLOR_COMPONENT_R_BIT;
if (green)
mask |= VK_COLOR_COMPONENT_G_BIT;
if (blue)
mask |= VK_COLOR_COMPONENT_B_BIT;
if (alpha)
mask |= VK_COLOR_COMPONENT_A_BIT;
g_vkStateColorWriteMask = mask;
if (g_vkIsRecordingCommandList)
g_vkRecordingHasStateChanges = true;
}
void C4JRender::StateSetDepthTestEnable(bool enable) {
g_vkStateDepthTestEnable = enable;
if (g_vkIsRecordingCommandList)
g_vkRecordingHasStateChanges = true;
}
void C4JRender::StateSetAlphaTestEnable(bool enable) {
g_vkStateAlphaTestEnable = enable;
if (g_vkIsRecordingCommandList)
g_vkRecordingHasAlphaStateChanges = true;
}
void C4JRender::StateSetDepthSlopeAndBias(float, float) {}
void C4JRender::StateSetFogEnable(bool) {}
void C4JRender::StateSetFogMode(int) {}
void C4JRender::StateSetFogNearDistance(float) {}
void C4JRender::StateSetFogFarDistance(float) {}
void C4JRender::StateSetFogDensity(float) {}
void C4JRender::StateSetFogColour(float, float, float) {}
void C4JRender::StateSetLightingEnable(bool) {}
void C4JRender::StateSetVertexTextureUV(float, float) {}
void C4JRender::StateSetLightColour(int, float, float, float) {}
void C4JRender::StateSetLightAmbientColour(float, float, float) {}
void C4JRender::StateSetLightDirection(int, float, float, float) {}
void C4JRender::StateSetLightEnable(int, bool) {}
void C4JRender::StateSetViewport(eViewportType) {}
void C4JRender::StateSetEnableViewportClipPlanes(bool) {}
void C4JRender::StateSetTexGenCol(int, float, float, float, float, bool) {}
void C4JRender::StateSetStencil(int, uint8_t, uint8_t, uint8_t) {}
void C4JRender::StateSetForceLOD(int) {}
// ============================================================================
// Events + PLM
// ============================================================================
void C4JRender::BeginEvent(LPCWSTR) {}
void C4JRender::EndEvent() {}
void C4JRender::Suspend() {}
bool C4JRender::Suspended() { return false; }
void C4JRender::Resume() {}