From 5de34efe2acdd0e784280d797cd705440d703e5f Mon Sep 17 00:00:00 2001 From: Kitrae Date: Wed, 4 Mar 2026 18:49:19 -0600 Subject: [PATCH] vulkan: add initial Windows64 renderer backend and chunk texturing fixes Introduce a Windows64 Vulkan renderer implementation under C4JRender with the core runtime path for init, frame recording, and present. Includes Vulkan swapchain/device setup, triangle pipeline state management, command-list draw replay, dynamic vertex streaming, texture object APIs, descriptor binding, alpha-test path, and UI overlay bridge integration. Add Vulkan shader blobs for world geometry/UI and bridge header used by the Windows64 path. Chunk rendering updates for Vulkan stability: - keep compact/compressed chunk vertices disabled on Windows until the compact decode path is fully ported - decode legacy no-mipmap U encoding (u + 1.0f flag) in the Vulkan vertex expansion path - fix command-list replay so texture/alpha state is only restored when it was explicitly recorded, preserving runtime texture binds for chunk lists --- Minecraft.Client/Chunk.cpp | 8 + Minecraft.Client/Tesselator.cpp | 7 +- .../Windows64/C4JRender_Vulkan.cpp | 4608 +++++++++++++++++ .../Windows64/VulkanTriangleShaders.inl | 145 + Minecraft.Client/Windows64/VulkanUIBridge.h | 6 + .../Windows64/VulkanUIShaders.inl | 87 + 6 files changed, 4860 insertions(+), 1 deletion(-) create mode 100644 Minecraft.Client/Windows64/C4JRender_Vulkan.cpp create mode 100644 Minecraft.Client/Windows64/VulkanTriangleShaders.inl create mode 100644 Minecraft.Client/Windows64/VulkanUIBridge.h create mode 100644 Minecraft.Client/Windows64/VulkanUIShaders.inl diff --git a/Minecraft.Client/Chunk.cpp b/Minecraft.Client/Chunk.cpp index 99933db3..bca0c93e 100644 --- a/Minecraft.Client/Chunk.cpp +++ b/Minecraft.Client/Chunk.cpp @@ -385,7 +385,11 @@ void Chunk::rebuild() MemSect(0); glPushMatrix(); glDepthMask(true); // 4J added + #if defined(_XBOX) || defined(_XBOX_ONE) || defined(__PS3__) || defined(__ORBIS__) || defined(__PSVITA__) t->useCompactVertices(true); // 4J added + #else + t->useCompactVertices(false); // compact path on w vulkan caused uv decode issues, keep this false + #endif translateToPos(); float ss = 1.000001f; // 4J - have removed this scale as I don't think we should need it, and have now optimised the vertex @@ -704,7 +708,11 @@ void Chunk::rebuild_SPU() MemSect(0); glPushMatrix(); glDepthMask(true); // 4J added + #if defined(_XBOX) || defined(_XBOX_ONE) || defined(__PS3__) || defined(__ORBIS__) || defined(__PSVITA__) t->useCompactVertices(true); // 4J added + #else + t->useCompactVertices(false); // compact path on w vulkan caused uv decode issues, keep this false + #endif translateToPos(); float ss = 1.000001f; // 4J - have removed this scale as I don't think we should need it, and have now optimised the vertex diff --git a/Minecraft.Client/Tesselator.cpp b/Minecraft.Client/Tesselator.cpp index 366b09e3..d424950e 100644 --- a/Minecraft.Client/Tesselator.cpp +++ b/Minecraft.Client/Tesselator.cpp @@ -231,7 +231,12 @@ void Tesselator::useProjectedTexture(bool enable) void Tesselator::useCompactVertices(bool enable) { +#if defined(_WIN32) && !defined(_XBOX) && !defined(_XBOX_ONE) + // windows vulkan: keep compact off for now, decoder not finished. + useCompactFormat360 = false; +#else useCompactFormat360 = enable; +#endif } bool Tesselator::getCompactVertices() @@ -1077,4 +1082,4 @@ bool Tesselator::hasMaxVertices() #else return false; #endif -} \ No newline at end of file +} diff --git a/Minecraft.Client/Windows64/C4JRender_Vulkan.cpp b/Minecraft.Client/Windows64/C4JRender_Vulkan.cpp new file mode 100644 index 00000000..322f564f --- /dev/null +++ b/Minecraft.Client/Windows64/C4JRender_Vulkan.cpp @@ -0,0 +1,4608 @@ +// ============================================================================ +// C4JRender_Vulkan.cpp - Windows Vulkan backend for C4JRender. +// ============================================================================ +#include "stdafx.h" + +#define VK_USE_PLATFORM_WIN32_KHR +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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 g_vkSwapImages; +static std::vector g_vkSwapImageViews; +static std::vector g_vkFramebuffers; +static std::vector 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 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 levelData; +}; + +static std::unordered_map 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 g_vkFrameVertexData; +static std::vector 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 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 vertexData; + // Pre-expanded to BootstrapVertex layout (RGBA + triangle list) for fast replay. + std::vector 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>> + 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 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(depthCompareOp) & 0xffull) << 8; + key |= (static_cast(srcBlendFactor) & 0xffull) << 16; + key |= (static_cast(dstBlendFactor) & 0xffull) << 24; + key |= (static_cast(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(width) * static_cast(height) * 4u; + if (bytes < expectedBytes) + return; + + g_vkUiUpload.width = static_cast(width); + g_vkUiUpload.height = static_cast(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 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 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 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(width) * static_cast(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(a) << 24) | (static_cast(r) << 16) | + (static_cast(g) << 8) | static_cast(b); + } + + pSrcInfo->Width = static_cast(width); + pSrcInfo->Height = static_cast(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(width) * static_cast(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(data); + uint8_t *dstBase = dst.pixels.data(); + const size_t srcRowBytes = static_cast(width) * 4u; + const size_t dstRowBytes = static_cast(dst.width) * 4u; + for (uint32_t y = 0; y < height; ++y) { + uint8_t *dstRow = dstBase + + (static_cast(yoffset + y) * dstRowBytes) + + static_cast(xoffset) * 4u; + const uint8_t *srcRow = src + static_cast(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(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(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(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(width) * static_cast(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(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(xoffset), + static_cast(yoffset), 0}; + copyRegion.imageExtent = {width, height, 1}; + vkCmdCopyBufferToImage(uploadCmd, stagingBuffer, tex.image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©Region); + + 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(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(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(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(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 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(g_vkSwapExtent.width); + viewport.height = static_cast(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(g_vkSwapExtent.width); + viewport.height = static_cast(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 &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 &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(col) * cellW; + const float y0 = y + static_cast(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 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(col)) * cellW; + const float y0 = yStart + static_cast(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(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(oldSize / kVertexStridePF3TF2CB4NB4XW1); + draw.vertexCount = static_cast(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(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 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 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 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, ©Region); + + 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(draw.vertexCount); + } + g_vkPerfDrawCount = static_cast(g_vkQueuedDraws.size()); + g_vkPerfVertexCount = + (frameVertexCount > 0xffffffffull) ? 0xffffffffu + : static_cast(frameVertexCount); + g_vkPerfUploadBytes = (g_vkFrameVertexData.size() > 0xffffffffull) + ? 0xffffffffu + : static_cast(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(nowCounter.QuadPart - g_vkPerfLastPresent.QuadPart) / + static_cast(g_vkPerfFreq.QuadPart); + if (dtSeconds > 0.000001) { + const float instFps = static_cast(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(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((packed >> 11) & 0x1f); + const uint8_t g6 = static_cast((packed >> 5) & 0x3f); + const uint8_t b5 = static_cast(packed & 0x1f); + r = static_cast((r5 * 255) / 31); + g = static_cast((g6 * 255) / 63); + b = static_cast((b5 * 255) / 31); + a = 255; +} + +static inline uint8_t modulate8(uint8_t value, float mul) { + int out = static_cast(static_cast(value) * mul + 0.5f); + if (out < 0) + out = 0; + if (out > 255) + out = 255; + return static_cast(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 &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(count) + : static_cast((count / 4) * 6); + if (outVertexCount == 0) + return 0; + + const size_t oldSize = out.size(); + out.resize(oldSize + + static_cast(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(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(src); + const float x = static_cast(s[0]) / 1024.0f; + const float y = static_cast(s[1]) / 1024.0f; + const float z = static_cast(s[2]) / 1024.0f; + const uint16_t encodedColour = static_cast(s[3]); + const uint16_t packed565 = static_cast(encodedColour + 32768u); + const float u = static_cast(s[4]) / 8192.0f; + const float v = static_cast(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(vertexCount) * kVertexStridePF3TF2CB4NB4XW1; + g_vkFrameVertexData.resize(oldSize + addBytes); + std::memcpy(g_vkFrameVertexData.data() + oldSize, vertexBytes, addBytes); + + VulkanQueuedDraw draw = {}; + draw.firstVertex = static_cast(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(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(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(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 commandListsLock(g_vkCommandListsMutex); + int first = g_nextCommandBufferId; + g_nextCommandBufferId += count; + for (int i = 0; i < count; ++i) { + g_vkCommandLists[first + i] = std::make_shared>(); + } + return first; +} +void C4JRender::CBuffDelete(int first, int count) { + if (count <= 0) + count = 1; + { + std::lock_guard 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 commandListsLock(g_vkCommandListsMutex); + if (g_vkCommandLists.find(index) == g_vkCommandLists.end()) + g_vkCommandLists[index] = std::make_shared>(); + } + 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 commandListsLock(g_vkCommandListsMutex); + g_vkCommandLists[index] = std::make_shared>(); +} +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 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(bytes); +} +void C4JRender::CBuffEnd() { + if (g_vkIsRecordingCommandList && g_vkRecordingCommandListIndex >= 0) { + std::lock_guard commandListsLock(g_vkCommandListsMutex); + g_vkCommandLists[g_vkRecordingCommandListIndex] = + std::make_shared>( + 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> calls; + { + std::lock_guard 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(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(levels); + } +} + +int C4JRender::TextureGetTextureLevels() { + auto it = g_vkTextures.find(g_vkStateTextureId); + if (it != g_vkTextures.end() && it->second.mipLevels > 0) { + return static_cast(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(level); + cacheTextureLevelPixels(tex, uploadLevel, static_cast(width), + static_cast(height), data); + + uint32_t requestedLevels = + static_cast((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((g_vkPendingTextureLevels > 0) ? g_vkPendingTextureLevels + : 1); + if (targetLevels <= uploadLevel) + targetLevels = uploadLevel + 1; + + uint32_t baseWidth = static_cast(width); + uint32_t baseHeight = static_cast(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(width), + static_cast(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(level); + const uint32_t uWidth = static_cast(width); + const uint32_t uHeight = static_cast(height); + const uint32_t uX = static_cast(xoffset); + const uint32_t uY = static_cast(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((colour >> 24) & 0xff) / 255.0f; + const float r = static_cast((colour >> 16) & 0xff) / 255.0f; + const float g = static_cast((colour >> 8) & 0xff) / 255.0f; + const float b = static_cast(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() {} diff --git a/Minecraft.Client/Windows64/VulkanTriangleShaders.inl b/Minecraft.Client/Windows64/VulkanTriangleShaders.inl new file mode 100644 index 00000000..311898ff --- /dev/null +++ b/Minecraft.Client/Windows64/VulkanTriangleShaders.inl @@ -0,0 +1,145 @@ +// Auto-generated SPIR-V for DrawVertices textured + alpha-test pipeline. +static const uint32_t kTriangleVertSpv[] = { + 0x07230203, 0x00010000, 0x0008000b, 0x0000003f, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, + 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, + 0x000b000f, 0x00000000, 0x00000004, 0x6e69616d, 0x00000000, 0x00000015, 0x00000031, 0x00000037, + 0x00000039, 0x0000003b, 0x0000003d, 0x00030003, 0x00000002, 0x000001c2, 0x00040005, 0x00000004, + 0x6e69616d, 0x00000000, 0x00030005, 0x00000009, 0x00736f70, 0x00060005, 0x0000000b, 0x68737550, + 0x736e6f43, 0x746e6174, 0x00000073, 0x00040006, 0x0000000b, 0x00000000, 0x0070766d, 0x00060006, + 0x0000000b, 0x00000001, 0x68706c61, 0x61745361, 0x00006574, 0x00030005, 0x0000000d, 0x00006370, + 0x00040005, 0x00000015, 0x6f506e69, 0x00000073, 0x00060005, 0x0000002f, 0x505f6c67, 0x65567265, + 0x78657472, 0x00000000, 0x00060006, 0x0000002f, 0x00000000, 0x505f6c67, 0x7469736f, 0x006e6f69, + 0x00070006, 0x0000002f, 0x00000001, 0x505f6c67, 0x746e696f, 0x657a6953, 0x00000000, 0x00070006, + 0x0000002f, 0x00000002, 0x435f6c67, 0x4470696c, 0x61747369, 0x0065636e, 0x00070006, 0x0000002f, + 0x00000003, 0x435f6c67, 0x446c6c75, 0x61747369, 0x0065636e, 0x00030005, 0x00000031, 0x00000000, + 0x00030005, 0x00000037, 0x00565576, 0x00040005, 0x00000039, 0x56556e69, 0x00000000, 0x00040005, + 0x0000003b, 0x6c6f4376, 0x0000726f, 0x00040005, 0x0000003d, 0x6f436e69, 0x00726f6c, 0x00030047, + 0x0000000b, 0x00000002, 0x00040048, 0x0000000b, 0x00000000, 0x00000005, 0x00050048, 0x0000000b, + 0x00000000, 0x00000007, 0x00000010, 0x00050048, 0x0000000b, 0x00000000, 0x00000023, 0x00000000, + 0x00050048, 0x0000000b, 0x00000001, 0x00000023, 0x00000040, 0x00040047, 0x00000015, 0x0000001e, + 0x00000000, 0x00030047, 0x0000002f, 0x00000002, 0x00050048, 0x0000002f, 0x00000000, 0x0000000b, + 0x00000000, 0x00050048, 0x0000002f, 0x00000001, 0x0000000b, 0x00000001, 0x00050048, 0x0000002f, + 0x00000002, 0x0000000b, 0x00000003, 0x00050048, 0x0000002f, 0x00000003, 0x0000000b, 0x00000004, + 0x00040047, 0x00000037, 0x0000001e, 0x00000000, 0x00040047, 0x00000039, 0x0000001e, 0x00000001, + 0x00040047, 0x0000003b, 0x0000001e, 0x00000001, 0x00040047, 0x0000003d, 0x0000001e, 0x00000002, + 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000020, + 0x00040017, 0x00000007, 0x00000006, 0x00000004, 0x00040020, 0x00000008, 0x00000007, 0x00000007, + 0x00040018, 0x0000000a, 0x00000007, 0x00000004, 0x0004001e, 0x0000000b, 0x0000000a, 0x00000007, + 0x00040020, 0x0000000c, 0x00000009, 0x0000000b, 0x0004003b, 0x0000000c, 0x0000000d, 0x00000009, + 0x00040015, 0x0000000e, 0x00000020, 0x00000001, 0x0004002b, 0x0000000e, 0x0000000f, 0x00000000, + 0x00040020, 0x00000010, 0x00000009, 0x0000000a, 0x00040017, 0x00000013, 0x00000006, 0x00000003, + 0x00040020, 0x00000014, 0x00000001, 0x00000013, 0x0004003b, 0x00000014, 0x00000015, 0x00000001, + 0x0004002b, 0x00000006, 0x00000017, 0x3f800000, 0x00040015, 0x0000001d, 0x00000020, 0x00000000, + 0x0004002b, 0x0000001d, 0x0000001e, 0x00000001, 0x00040020, 0x0000001f, 0x00000007, 0x00000006, + 0x0004002b, 0x0000001d, 0x00000024, 0x00000002, 0x0004002b, 0x0000001d, 0x00000027, 0x00000003, + 0x0004002b, 0x00000006, 0x0000002b, 0x3f000000, 0x0004001c, 0x0000002e, 0x00000006, 0x0000001e, + 0x0006001e, 0x0000002f, 0x00000007, 0x00000006, 0x0000002e, 0x0000002e, 0x00040020, 0x00000030, + 0x00000003, 0x0000002f, 0x0004003b, 0x00000030, 0x00000031, 0x00000003, 0x00040020, 0x00000033, + 0x00000003, 0x00000007, 0x00040017, 0x00000035, 0x00000006, 0x00000002, 0x00040020, 0x00000036, + 0x00000003, 0x00000035, 0x0004003b, 0x00000036, 0x00000037, 0x00000003, 0x00040020, 0x00000038, + 0x00000001, 0x00000035, 0x0004003b, 0x00000038, 0x00000039, 0x00000001, 0x0004003b, 0x00000033, + 0x0000003b, 0x00000003, 0x00040020, 0x0000003c, 0x00000001, 0x00000007, 0x0004003b, 0x0000003c, + 0x0000003d, 0x00000001, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, + 0x00000005, 0x0004003b, 0x00000008, 0x00000009, 0x00000007, 0x00050041, 0x00000010, 0x00000011, + 0x0000000d, 0x0000000f, 0x0004003d, 0x0000000a, 0x00000012, 0x00000011, 0x0004003d, 0x00000013, + 0x00000016, 0x00000015, 0x00050051, 0x00000006, 0x00000018, 0x00000016, 0x00000000, 0x00050051, + 0x00000006, 0x00000019, 0x00000016, 0x00000001, 0x00050051, 0x00000006, 0x0000001a, 0x00000016, + 0x00000002, 0x00070050, 0x00000007, 0x0000001b, 0x00000018, 0x00000019, 0x0000001a, 0x00000017, + 0x00050091, 0x00000007, 0x0000001c, 0x00000012, 0x0000001b, 0x0003003e, 0x00000009, 0x0000001c, + 0x00050041, 0x0000001f, 0x00000020, 0x00000009, 0x0000001e, 0x0004003d, 0x00000006, 0x00000021, + 0x00000020, 0x0004007f, 0x00000006, 0x00000022, 0x00000021, 0x00050041, 0x0000001f, 0x00000023, + 0x00000009, 0x0000001e, 0x0003003e, 0x00000023, 0x00000022, 0x00050041, 0x0000001f, 0x00000025, + 0x00000009, 0x00000024, 0x0004003d, 0x00000006, 0x00000026, 0x00000025, 0x00050041, 0x0000001f, + 0x00000028, 0x00000009, 0x00000027, 0x0004003d, 0x00000006, 0x00000029, 0x00000028, 0x00050081, + 0x00000006, 0x0000002a, 0x00000026, 0x00000029, 0x00050085, 0x00000006, 0x0000002c, 0x0000002a, + 0x0000002b, 0x00050041, 0x0000001f, 0x0000002d, 0x00000009, 0x00000024, 0x0003003e, 0x0000002d, + 0x0000002c, 0x0004003d, 0x00000007, 0x00000032, 0x00000009, 0x00050041, 0x00000033, 0x00000034, + 0x00000031, 0x0000000f, 0x0003003e, 0x00000034, 0x00000032, 0x0004003d, 0x00000035, 0x0000003a, + 0x00000039, 0x0003003e, 0x00000037, 0x0000003a, 0x0004003d, 0x00000007, 0x0000003e, 0x0000003d, + 0x0003003e, 0x0000003b, 0x0000003e, 0x000100fd, 0x00010038, +}; + +static const uint32_t kTriangleFragSpv[] = { + 0x07230203, 0x00010000, 0x0008000b, 0x00000077, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, + 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, + 0x0008000f, 0x00000004, 0x00000004, 0x6e69616d, 0x00000000, 0x0000004a, 0x0000004e, 0x00000075, + 0x00030010, 0x00000004, 0x00000007, 0x00030003, 0x00000002, 0x000001c2, 0x00040005, 0x00000004, + 0x6e69616d, 0x00000000, 0x00080005, 0x0000000f, 0x68706c61, 0x73655461, 0x73615074, 0x31662873, + 0x3b31693b, 0x003b3166, 0x00030005, 0x0000000c, 0x00000061, 0x00040005, 0x0000000d, 0x636e7566, + 0x00000000, 0x00040005, 0x0000000e, 0x56666572, 0x00006c61, 0x00040005, 0x00000042, 0x6f6c6f63, + 0x00000072, 0x00040005, 0x00000046, 0x78655475, 0x00000000, 0x00030005, 0x0000004a, 0x00565576, + 0x00040005, 0x0000004e, 0x6c6f4376, 0x0000726f, 0x00060005, 0x00000052, 0x68737550, 0x736e6f43, + 0x746e6174, 0x00000073, 0x00040006, 0x00000052, 0x00000000, 0x0070766d, 0x00060006, 0x00000052, + 0x00000001, 0x68706c61, 0x61745361, 0x00006574, 0x00030005, 0x00000054, 0x00006370, 0x00040005, + 0x0000005f, 0x636e7566, 0x00000000, 0x00040005, 0x00000065, 0x61726170, 0x0000006d, 0x00040005, + 0x00000069, 0x61726170, 0x0000006d, 0x00040005, 0x0000006b, 0x61726170, 0x0000006d, 0x00050005, + 0x00000075, 0x4374756f, 0x726f6c6f, 0x00000000, 0x00040047, 0x00000046, 0x00000021, 0x00000000, + 0x00040047, 0x00000046, 0x00000022, 0x00000000, 0x00040047, 0x0000004a, 0x0000001e, 0x00000000, + 0x00040047, 0x0000004e, 0x0000001e, 0x00000001, 0x00030047, 0x00000052, 0x00000002, 0x00040048, + 0x00000052, 0x00000000, 0x00000005, 0x00050048, 0x00000052, 0x00000000, 0x00000007, 0x00000010, + 0x00050048, 0x00000052, 0x00000000, 0x00000023, 0x00000000, 0x00050048, 0x00000052, 0x00000001, + 0x00000023, 0x00000040, 0x00040047, 0x00000075, 0x0000001e, 0x00000000, 0x00020013, 0x00000002, + 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040020, 0x00000007, + 0x00000007, 0x00000006, 0x00040015, 0x00000008, 0x00000020, 0x00000001, 0x00040020, 0x00000009, + 0x00000007, 0x00000008, 0x00020014, 0x0000000a, 0x00060021, 0x0000000b, 0x0000000a, 0x00000007, + 0x00000009, 0x00000007, 0x0003002a, 0x0000000a, 0x0000001c, 0x0004002b, 0x00000006, 0x00000026, + 0x3b808081, 0x00030029, 0x0000000a, 0x0000003b, 0x00040017, 0x00000040, 0x00000006, 0x00000004, + 0x00040020, 0x00000041, 0x00000007, 0x00000040, 0x00090019, 0x00000043, 0x00000006, 0x00000001, + 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x0003001b, 0x00000044, 0x00000043, + 0x00040020, 0x00000045, 0x00000000, 0x00000044, 0x0004003b, 0x00000045, 0x00000046, 0x00000000, + 0x00040017, 0x00000048, 0x00000006, 0x00000002, 0x00040020, 0x00000049, 0x00000001, 0x00000048, + 0x0004003b, 0x00000049, 0x0000004a, 0x00000001, 0x00040020, 0x0000004d, 0x00000001, 0x00000040, + 0x0004003b, 0x0000004d, 0x0000004e, 0x00000001, 0x00040018, 0x00000051, 0x00000040, 0x00000004, + 0x0004001e, 0x00000052, 0x00000051, 0x00000040, 0x00040020, 0x00000053, 0x00000009, 0x00000052, + 0x0004003b, 0x00000053, 0x00000054, 0x00000009, 0x0004002b, 0x00000008, 0x00000055, 0x00000001, + 0x00040015, 0x00000056, 0x00000020, 0x00000000, 0x0004002b, 0x00000056, 0x00000057, 0x00000000, + 0x00040020, 0x00000058, 0x00000009, 0x00000006, 0x0004002b, 0x00000006, 0x0000005b, 0x3f000000, + 0x0004002b, 0x00000056, 0x00000060, 0x00000002, 0x0004002b, 0x00000056, 0x00000066, 0x00000003, + 0x0004002b, 0x00000056, 0x0000006c, 0x00000001, 0x00040020, 0x00000074, 0x00000003, 0x00000040, + 0x0004003b, 0x00000074, 0x00000075, 0x00000003, 0x00050036, 0x00000002, 0x00000004, 0x00000000, + 0x00000003, 0x000200f8, 0x00000005, 0x0004003b, 0x00000041, 0x00000042, 0x00000007, 0x0004003b, + 0x00000009, 0x0000005f, 0x00000007, 0x0004003b, 0x00000007, 0x00000065, 0x00000007, 0x0004003b, + 0x00000009, 0x00000069, 0x00000007, 0x0004003b, 0x00000007, 0x0000006b, 0x00000007, 0x0004003d, + 0x00000044, 0x00000047, 0x00000046, 0x0004003d, 0x00000048, 0x0000004b, 0x0000004a, 0x00050057, + 0x00000040, 0x0000004c, 0x00000047, 0x0000004b, 0x0004003d, 0x00000040, 0x0000004f, 0x0000004e, + 0x00050085, 0x00000040, 0x00000050, 0x0000004c, 0x0000004f, 0x0003003e, 0x00000042, 0x00000050, + 0x00060041, 0x00000058, 0x00000059, 0x00000054, 0x00000055, 0x00000057, 0x0004003d, 0x00000006, + 0x0000005a, 0x00000059, 0x000500ba, 0x0000000a, 0x0000005c, 0x0000005a, 0x0000005b, 0x000300f7, + 0x0000005e, 0x00000000, 0x000400fa, 0x0000005c, 0x0000005d, 0x0000005e, 0x000200f8, 0x0000005d, + 0x00060041, 0x00000058, 0x00000061, 0x00000054, 0x00000055, 0x00000060, 0x0004003d, 0x00000006, + 0x00000062, 0x00000061, 0x00050081, 0x00000006, 0x00000063, 0x00000062, 0x0000005b, 0x0004006e, + 0x00000008, 0x00000064, 0x00000063, 0x0003003e, 0x0000005f, 0x00000064, 0x00050041, 0x00000007, + 0x00000067, 0x00000042, 0x00000066, 0x0004003d, 0x00000006, 0x00000068, 0x00000067, 0x0003003e, + 0x00000065, 0x00000068, 0x0004003d, 0x00000008, 0x0000006a, 0x0000005f, 0x0003003e, 0x00000069, + 0x0000006a, 0x00060041, 0x00000058, 0x0000006d, 0x00000054, 0x00000055, 0x0000006c, 0x0004003d, + 0x00000006, 0x0000006e, 0x0000006d, 0x0003003e, 0x0000006b, 0x0000006e, 0x00070039, 0x0000000a, + 0x0000006f, 0x0000000f, 0x00000065, 0x00000069, 0x0000006b, 0x000400a8, 0x0000000a, 0x00000070, + 0x0000006f, 0x000300f7, 0x00000072, 0x00000000, 0x000400fa, 0x00000070, 0x00000071, 0x00000072, + 0x000200f8, 0x00000071, 0x000100fc, 0x000200f8, 0x00000072, 0x000200f9, 0x0000005e, 0x000200f8, + 0x0000005e, 0x0004003d, 0x00000040, 0x00000076, 0x00000042, 0x0003003e, 0x00000075, 0x00000076, + 0x000100fd, 0x00010038, 0x00050036, 0x0000000a, 0x0000000f, 0x00000000, 0x0000000b, 0x00030037, + 0x00000007, 0x0000000c, 0x00030037, 0x00000009, 0x0000000d, 0x00030037, 0x00000007, 0x0000000e, + 0x000200f8, 0x00000010, 0x0004003d, 0x00000008, 0x00000011, 0x0000000d, 0x000300f7, 0x0000001b, + 0x00000000, 0x001300fb, 0x00000011, 0x0000001a, 0x00000001, 0x00000012, 0x00000002, 0x00000013, + 0x00000003, 0x00000014, 0x00000004, 0x00000015, 0x00000005, 0x00000016, 0x00000006, 0x00000017, + 0x00000007, 0x00000018, 0x00000008, 0x00000019, 0x000200f8, 0x0000001a, 0x000200fe, 0x0000003b, + 0x000200f8, 0x00000012, 0x000200fe, 0x0000001c, 0x000200f8, 0x00000013, 0x0004003d, 0x00000006, + 0x0000001e, 0x0000000c, 0x0004003d, 0x00000006, 0x0000001f, 0x0000000e, 0x000500b8, 0x0000000a, + 0x00000020, 0x0000001e, 0x0000001f, 0x000200fe, 0x00000020, 0x000200f8, 0x00000014, 0x0004003d, + 0x00000006, 0x00000022, 0x0000000c, 0x0004003d, 0x00000006, 0x00000023, 0x0000000e, 0x00050083, + 0x00000006, 0x00000024, 0x00000022, 0x00000023, 0x0006000c, 0x00000006, 0x00000025, 0x00000001, + 0x00000004, 0x00000024, 0x000500bc, 0x0000000a, 0x00000027, 0x00000025, 0x00000026, 0x000200fe, + 0x00000027, 0x000200f8, 0x00000015, 0x0004003d, 0x00000006, 0x00000029, 0x0000000c, 0x0004003d, + 0x00000006, 0x0000002a, 0x0000000e, 0x000500bc, 0x0000000a, 0x0000002b, 0x00000029, 0x0000002a, + 0x000200fe, 0x0000002b, 0x000200f8, 0x00000016, 0x0004003d, 0x00000006, 0x0000002d, 0x0000000c, + 0x0004003d, 0x00000006, 0x0000002e, 0x0000000e, 0x000500ba, 0x0000000a, 0x0000002f, 0x0000002d, + 0x0000002e, 0x000200fe, 0x0000002f, 0x000200f8, 0x00000017, 0x0004003d, 0x00000006, 0x00000031, + 0x0000000c, 0x0004003d, 0x00000006, 0x00000032, 0x0000000e, 0x00050083, 0x00000006, 0x00000033, + 0x00000031, 0x00000032, 0x0006000c, 0x00000006, 0x00000034, 0x00000001, 0x00000004, 0x00000033, + 0x000500ba, 0x0000000a, 0x00000035, 0x00000034, 0x00000026, 0x000200fe, 0x00000035, 0x000200f8, + 0x00000018, 0x0004003d, 0x00000006, 0x00000037, 0x0000000c, 0x0004003d, 0x00000006, 0x00000038, + 0x0000000e, 0x000500be, 0x0000000a, 0x00000039, 0x00000037, 0x00000038, 0x000200fe, 0x00000039, + 0x000200f8, 0x00000019, 0x000200fe, 0x0000003b, 0x000200f8, 0x0000001b, 0x000100ff, 0x00010038, + +}; + diff --git a/Minecraft.Client/Windows64/VulkanUIBridge.h b/Minecraft.Client/Windows64/VulkanUIBridge.h new file mode 100644 index 00000000..b1b5b1f5 --- /dev/null +++ b/Minecraft.Client/Windows64/VulkanUIBridge.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +void VulkanSubmitIggyOverlayBGRA(int width, int height, const void *pixels, + size_t bytes); diff --git a/Minecraft.Client/Windows64/VulkanUIShaders.inl b/Minecraft.Client/Windows64/VulkanUIShaders.inl new file mode 100644 index 00000000..6444dce7 --- /dev/null +++ b/Minecraft.Client/Windows64/VulkanUIShaders.inl @@ -0,0 +1,87 @@ +// Auto-generated SPIR-V for Vulkan UI composite pipeline. +static const uint32_t kUiCompositeVertSpv[] = { + 0x07230203, 0x00010000, 0x000d000b, 0x0000003f, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, + 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, + 0x000b000f, 0x00000000, 0x00000004, 0x6e69616d, 0x00000000, 0x00000015, 0x00000031, 0x00000037, + 0x00000039, 0x0000003b, 0x0000003d, 0x00030003, 0x00000002, 0x000001c2, 0x000a0004, 0x475f4c47, + 0x4c474f4f, 0x70635f45, 0x74735f70, 0x5f656c79, 0x656e696c, 0x7269645f, 0x69746365, 0x00006576, + 0x00080004, 0x475f4c47, 0x4c474f4f, 0x6e695f45, 0x64756c63, 0x69645f65, 0x74636572, 0x00657669, + 0x00040005, 0x00000004, 0x6e69616d, 0x00000000, 0x00030005, 0x00000009, 0x00736f70, 0x00040005, + 0x0000000b, 0x68737550, 0x00000000, 0x00040006, 0x0000000b, 0x00000000, 0x0070766d, 0x00030005, + 0x0000000d, 0x00006370, 0x00040005, 0x00000015, 0x6f506e69, 0x00000073, 0x00060005, 0x0000002f, + 0x505f6c67, 0x65567265, 0x78657472, 0x00000000, 0x00060006, 0x0000002f, 0x00000000, 0x505f6c67, + 0x7469736f, 0x006e6f69, 0x00070006, 0x0000002f, 0x00000001, 0x505f6c67, 0x746e696f, 0x657a6953, + 0x00000000, 0x00070006, 0x0000002f, 0x00000002, 0x435f6c67, 0x4470696c, 0x61747369, 0x0065636e, + 0x00070006, 0x0000002f, 0x00000003, 0x435f6c67, 0x446c6c75, 0x61747369, 0x0065636e, 0x00030005, + 0x00000031, 0x00000000, 0x00030005, 0x00000037, 0x00565576, 0x00040005, 0x00000039, 0x56556e69, + 0x00000000, 0x00040005, 0x0000003b, 0x6c6f4376, 0x0000726f, 0x00040005, 0x0000003d, 0x6f436e69, + 0x00726f6c, 0x00030047, 0x0000000b, 0x00000002, 0x00040048, 0x0000000b, 0x00000000, 0x00000005, + 0x00050048, 0x0000000b, 0x00000000, 0x00000007, 0x00000010, 0x00050048, 0x0000000b, 0x00000000, + 0x00000023, 0x00000000, 0x00040047, 0x00000015, 0x0000001e, 0x00000000, 0x00030047, 0x0000002f, + 0x00000002, 0x00050048, 0x0000002f, 0x00000000, 0x0000000b, 0x00000000, 0x00050048, 0x0000002f, + 0x00000001, 0x0000000b, 0x00000001, 0x00050048, 0x0000002f, 0x00000002, 0x0000000b, 0x00000003, + 0x00050048, 0x0000002f, 0x00000003, 0x0000000b, 0x00000004, 0x00040047, 0x00000037, 0x0000001e, + 0x00000000, 0x00040047, 0x00000039, 0x0000001e, 0x00000001, 0x00040047, 0x0000003b, 0x0000001e, + 0x00000001, 0x00040047, 0x0000003d, 0x0000001e, 0x00000002, 0x00020013, 0x00000002, 0x00030021, + 0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006, + 0x00000004, 0x00040020, 0x00000008, 0x00000007, 0x00000007, 0x00040018, 0x0000000a, 0x00000007, + 0x00000004, 0x0003001e, 0x0000000b, 0x0000000a, 0x00040020, 0x0000000c, 0x00000009, 0x0000000b, + 0x0004003b, 0x0000000c, 0x0000000d, 0x00000009, 0x00040015, 0x0000000e, 0x00000020, 0x00000001, + 0x0004002b, 0x0000000e, 0x0000000f, 0x00000000, 0x00040020, 0x00000010, 0x00000009, 0x0000000a, + 0x00040017, 0x00000013, 0x00000006, 0x00000003, 0x00040020, 0x00000014, 0x00000001, 0x00000013, + 0x0004003b, 0x00000014, 0x00000015, 0x00000001, 0x0004002b, 0x00000006, 0x00000017, 0x3f800000, + 0x00040015, 0x0000001d, 0x00000020, 0x00000000, 0x0004002b, 0x0000001d, 0x0000001e, 0x00000001, + 0x00040020, 0x0000001f, 0x00000007, 0x00000006, 0x0004002b, 0x0000001d, 0x00000024, 0x00000002, + 0x0004002b, 0x0000001d, 0x00000027, 0x00000003, 0x0004002b, 0x00000006, 0x0000002b, 0x3f000000, + 0x0004001c, 0x0000002e, 0x00000006, 0x0000001e, 0x0006001e, 0x0000002f, 0x00000007, 0x00000006, + 0x0000002e, 0x0000002e, 0x00040020, 0x00000030, 0x00000003, 0x0000002f, 0x0004003b, 0x00000030, + 0x00000031, 0x00000003, 0x00040020, 0x00000033, 0x00000003, 0x00000007, 0x00040017, 0x00000035, + 0x00000006, 0x00000002, 0x00040020, 0x00000036, 0x00000003, 0x00000035, 0x0004003b, 0x00000036, + 0x00000037, 0x00000003, 0x00040020, 0x00000038, 0x00000001, 0x00000035, 0x0004003b, 0x00000038, + 0x00000039, 0x00000001, 0x0004003b, 0x00000033, 0x0000003b, 0x00000003, 0x00040020, 0x0000003c, + 0x00000001, 0x00000007, 0x0004003b, 0x0000003c, 0x0000003d, 0x00000001, 0x00050036, 0x00000002, + 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x0004003b, 0x00000008, 0x00000009, + 0x00000007, 0x00050041, 0x00000010, 0x00000011, 0x0000000d, 0x0000000f, 0x0004003d, 0x0000000a, + 0x00000012, 0x00000011, 0x0004003d, 0x00000013, 0x00000016, 0x00000015, 0x00050051, 0x00000006, + 0x00000018, 0x00000016, 0x00000000, 0x00050051, 0x00000006, 0x00000019, 0x00000016, 0x00000001, + 0x00050051, 0x00000006, 0x0000001a, 0x00000016, 0x00000002, 0x00070050, 0x00000007, 0x0000001b, + 0x00000018, 0x00000019, 0x0000001a, 0x00000017, 0x00050091, 0x00000007, 0x0000001c, 0x00000012, + 0x0000001b, 0x0003003e, 0x00000009, 0x0000001c, 0x00050041, 0x0000001f, 0x00000020, 0x00000009, + 0x0000001e, 0x0004003d, 0x00000006, 0x00000021, 0x00000020, 0x0004007f, 0x00000006, 0x00000022, + 0x00000021, 0x00050041, 0x0000001f, 0x00000023, 0x00000009, 0x0000001e, 0x0003003e, 0x00000023, + 0x00000022, 0x00050041, 0x0000001f, 0x00000025, 0x00000009, 0x00000024, 0x0004003d, 0x00000006, + 0x00000026, 0x00000025, 0x00050041, 0x0000001f, 0x00000028, 0x00000009, 0x00000027, 0x0004003d, + 0x00000006, 0x00000029, 0x00000028, 0x00050081, 0x00000006, 0x0000002a, 0x00000026, 0x00000029, + 0x00050085, 0x00000006, 0x0000002c, 0x0000002a, 0x0000002b, 0x00050041, 0x0000001f, 0x0000002d, + 0x00000009, 0x00000024, 0x0003003e, 0x0000002d, 0x0000002c, 0x0004003d, 0x00000007, 0x00000032, + 0x00000009, 0x00050041, 0x00000033, 0x00000034, 0x00000031, 0x0000000f, 0x0003003e, 0x00000034, + 0x00000032, 0x0004003d, 0x00000035, 0x0000003a, 0x00000039, 0x0003003e, 0x00000037, 0x0000003a, + 0x0004003d, 0x00000007, 0x0000003e, 0x0000003d, 0x0003003e, 0x0000003b, 0x0000003e, 0x000100fd, + 0x00010038 +}; + +static const uint32_t kUiCompositeFragSpv[] = { + 0x07230203, 0x00010000, 0x000d000b, 0x00000018, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, + 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, + 0x0008000f, 0x00000004, 0x00000004, 0x6e69616d, 0x00000000, 0x00000009, 0x00000011, 0x00000015, + 0x00030010, 0x00000004, 0x00000007, 0x00030003, 0x00000002, 0x000001c2, 0x000a0004, 0x475f4c47, + 0x4c474f4f, 0x70635f45, 0x74735f70, 0x5f656c79, 0x656e696c, 0x7269645f, 0x69746365, 0x00006576, + 0x00080004, 0x475f4c47, 0x4c474f4f, 0x6e695f45, 0x64756c63, 0x69645f65, 0x74636572, 0x00657669, + 0x00040005, 0x00000004, 0x6e69616d, 0x00000000, 0x00050005, 0x00000009, 0x4374756f, 0x726f6c6f, + 0x00000000, 0x00040005, 0x0000000d, 0x78655475, 0x00000000, 0x00030005, 0x00000011, 0x00565576, + 0x00040005, 0x00000015, 0x6c6f4376, 0x0000726f, 0x00040047, 0x00000009, 0x0000001e, 0x00000000, + 0x00040047, 0x0000000d, 0x00000021, 0x00000000, 0x00040047, 0x0000000d, 0x00000022, 0x00000000, + 0x00040047, 0x00000011, 0x0000001e, 0x00000000, 0x00040047, 0x00000015, 0x0000001e, 0x00000001, + 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000020, + 0x00040017, 0x00000007, 0x00000006, 0x00000004, 0x00040020, 0x00000008, 0x00000003, 0x00000007, + 0x0004003b, 0x00000008, 0x00000009, 0x00000003, 0x00090019, 0x0000000a, 0x00000006, 0x00000001, + 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x0003001b, 0x0000000b, 0x0000000a, + 0x00040020, 0x0000000c, 0x00000000, 0x0000000b, 0x0004003b, 0x0000000c, 0x0000000d, 0x00000000, + 0x00040017, 0x0000000f, 0x00000006, 0x00000002, 0x00040020, 0x00000010, 0x00000001, 0x0000000f, + 0x0004003b, 0x00000010, 0x00000011, 0x00000001, 0x00040020, 0x00000014, 0x00000001, 0x00000007, + 0x0004003b, 0x00000014, 0x00000015, 0x00000001, 0x00050036, 0x00000002, 0x00000004, 0x00000000, + 0x00000003, 0x000200f8, 0x00000005, 0x0004003d, 0x0000000b, 0x0000000e, 0x0000000d, 0x0004003d, + 0x0000000f, 0x00000012, 0x00000011, 0x00050057, 0x00000007, 0x00000013, 0x0000000e, 0x00000012, + 0x0004003d, 0x00000007, 0x00000016, 0x00000015, 0x00050085, 0x00000007, 0x00000017, 0x00000013, + 0x00000016, 0x0003003e, 0x00000009, 0x00000017, 0x000100fd, 0x00010038 +};