//// Created by Bradley Austin Davis on 2016/08/07// Adapted for Vulkan in 2022-2025 by dr Karol Suprynowicz.// Copyright 2013-2018 High Fidelity, Inc.// Copyright 2023-2025 Overte e.V.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// SPDX-License-Identifier: Apache-2.0//#include "VKBackend.h" #include <mutex>#include <queue>#include <list>#include <functional>#include <glm/gtc/type_ptr.hpp> #include <QtCore/QProcessEnvironment> // For hash_combine#include <RegisteredMetaTypes.h> #include <gpu/TextureTable.h>#include <vk/Helpers.h>#include <vk/Version.h>#include <vk/Pipelines.h>#include "VKFramebuffer.h"#include "VKBuffer.h"#include "VKQuery.h" #include "VKForward.h"#include "VKShared.h"#include "VKTexture.h"#include "VKPipelineCache.h"#include <render-utils/ShaderConstants.h>#include "shared/FileUtils.h" #define FORCE_STRICT_TEXTURE 1 using namespace gpu;using namespace gpu::vk; // VKTODO: this is ugly solutionCache _cache; size_t VKBackend::UNIFORM_BUFFER_OFFSET_ALIGNMENT{ 4 }; static VKBackend* INSTANCE{ nullptr };static const char* VK_BACKEND_PROPERTY_NAME = "com.highfidelity.vk.backend"; BackendPointer VKBackend::createBackend() { // FIXME provide a mechanism to override the backend for testing // Where the gpuContext is initialized and where the TRUE Backend is created and assigned std::shared_ptr<VKBackend> result = std::make_shared<VKBackend>(); INSTANCE = result.get(); void* voidInstance = &(*result); qApp->setProperty(VK_BACKEND_PROPERTY_NAME, QVariant::fromValue(voidInstance)); return result;} VKBackend& getBackend() { if (!INSTANCE) { INSTANCE = static_cast<VKBackend*>(qApp->property(VK_BACKEND_PROPERTY_NAME).value<void*>()); } return *INSTANCE;} void VKBackend::init() {} VKBackend::VKBackend() { if (!_context.instance) {#ifdef QT_NO_DEBUG _context.setValidationEnabled(false); // VKTODO: find nicer way of toggling this#else _context.setValidationEnabled(true); // VKTODO: find nicer way of toggling this#endif _context.createInstance(); _context.createDevice(); } { VkPipelineCacheCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; std::vector<uint8_t> pipelineCacheData; if (vks::util::loadPipelineCacheData(pipelineCacheData) && !pipelineCacheData.empty()) { createInfo.pInitialData = pipelineCacheData.data(); createInfo.initialDataSize = pipelineCacheData.size(); } vkCreatePipelineCache(_context.device->logicalDevice, &createInfo, nullptr, &_pipelineCache); } // Get the graphics queue qCDebug(gpu_vk_logging) << "VK Version: " << vks::Version(_context.device->properties.apiVersion).toString().c_str(); qCDebug(gpu_vk_logging) << "VK Driver: " << vks::Version(_context.device->properties.driverVersion).toString().c_str(); qCDebug(gpu_vk_logging) << "VK Vendor ID: " << _context.device->properties.vendorID; qCDebug(gpu_vk_logging) << "VK Device ID: " << _context.device->properties.deviceID; qCDebug(gpu_vk_logging) << "VK Device Name: " << _context.device->properties.deviceName; qCDebug(gpu_vk_logging) << "VK Device Type: " << _context.device->properties.deviceType; // Add frame data to frames pool for (int i = 0; i < 3; i++) { _framePool.push_back(std::make_shared<FrameData>(this)); _framesToReuse.push_back(_framePool.back()); }} VKBackend::~VKBackend() { Q_ASSERT(isBackendShutdownComplete); // FIXME queue up all the trash calls _context.destroyContext();} void VKBackend::shutdown() { VK_CHECK_RESULT(vkQueueWaitIdle(_context.graphicsQueue)); VK_CHECK_RESULT(vkQueueWaitIdle(_context.transferQueue) ); VK_CHECK_RESULT(vkDeviceWaitIdle(_context.device->logicalDevice)); killTextureManagementStage(); _context.shutdownWindows(); // Release frames so their destructors can do a cleanup _currentFrame.reset(); _framesToReuse.resize(0); _framePool.resize(0); for (auto &module : _cache.moduleMap) { _context.recycler.trashVkShaderModule(module.second); } _cache.moduleMap.clear(); for (auto &renderPass : _cache.pipelineState._renderPassMap) { _context.recycler.trashVkRenderPass(renderPass.second); } _cache.pipelineState._renderPassMap.clear(); for (auto &pipeline : _cache.pipelineMap) { _context.recycler.trashVkDescriptorSetLayout(pipeline.second.uniformLayout); _context.recycler.trashVkDescriptorSetLayout(pipeline.second.storageLayout); _context.recycler.trashVkDescriptorSetLayout(pipeline.second.textureLayout); _context.recycler.trashVkPipelineLayout(pipeline.second.pipelineLayout); _context.recycler.trashVkPipeline(pipeline.second.pipeline); } _cache.pipelineMap.clear(); beforeShutdownCleanup(); { size_t pipelineCacheDataSize; VK_CHECK_RESULT(vkGetPipelineCacheData(_context.device->logicalDevice, _pipelineCache, &pipelineCacheDataSize, nullptr)); std::vector<uint8_t> pipelineCacheData; pipelineCacheData.resize(pipelineCacheDataSize); VK_CHECK_RESULT(vkGetPipelineCacheData(_context.device->logicalDevice, _pipelineCache, &pipelineCacheDataSize, nullptr)); if (!pipelineCacheData.empty()) { vks::util::savePipelineCacheData(pipelineCacheData); } } vkDestroyPipelineCache(_context.device->logicalDevice, _pipelineCache, nullptr); vmaDestroyAllocator(vks::Allocation::getAllocator()); isBackendShutdownComplete = true;} const std::string& VKBackend::getVersion() const { static const std::string VERSION{ "VK1.1" }; return VERSION;} bool VKBackend::isTextureManagementSparseEnabled() const { return _context.device->features.sparseResidencyImage2D == VK_TRUE;} bool VKBackend::supportedTextureFormat(const gpu::Element& format) const { switch (format.getSemantic()) { case COMPRESSED_BC1_SRGB: case COMPRESSED_BC1_SRGBA: case COMPRESSED_BC3_SRGBA: case COMPRESSED_BC4_RED: case COMPRESSED_BC5_XY: case COMPRESSED_BC6_RGB: case COMPRESSED_BC7_SRGBA: return _context.device->features.textureCompressionBC == VK_TRUE; case COMPRESSED_ETC2_RGB: case COMPRESSED_ETC2_SRGB: case COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA: case COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA: case COMPRESSED_ETC2_RGBA: case COMPRESSED_ETC2_SRGBA: case COMPRESSED_EAC_RED: case COMPRESSED_EAC_RED_SIGNED: case COMPRESSED_EAC_XY: case COMPRESSED_EAC_XY_SIGNED: return _context.device->features.textureCompressionETC2 == VK_TRUE; // VKTODO: //case COMPRESSED_ASTC_RGBA_10x10: //case COMPRESSED_ASTC_RGBA_10x5: //case COMPRESSED_ASTC_RGBA_10x6: //case COMPRESSED_ASTC_RGBA_10x8: //case COMPRESSED_ASTC_RGBA_12x10: //case COMPRESSED_ASTC_RGBA_12x12: //case COMPRESSED_ASTC_RGBA_4x4: //case COMPRESSED_ASTC_RGBA_5x4: //case COMPRESSED_ASTC_RGBA_5x5: //case COMPRESSED_ASTC_RGBA_6x5: //case COMPRESSED_ASTC_RGBA_6x6: //case COMPRESSED_ASTC_RGBA_8x5: //case COMPRESSED_ASTC_RGBA_8x6: //case COMPRESSED_ASTC_RGBA_8x8: //case COMPRESSED_ASTC_SRGB8_ALPHA8_10x10: //case COMPRESSED_ASTC_SRGB8_ALPHA8_10x5: //case COMPRESSED_ASTC_SRGB8_ALPHA8_10x6: //case COMPRESSED_ASTC_SRGB8_ALPHA8_10x8: //case COMPRESSED_ASTC_SRGB8_ALPHA8_12x10: //case COMPRESSED_ASTC_SRGB8_ALPHA8_12x12: //case COMPRESSED_ASTC_SRGB8_ALPHA8_4x4: //case COMPRESSED_ASTC_SRGB8_ALPHA8_5x4: //case COMPRESSED_ASTC_SRGB8_ALPHA8_5x5: //case COMPRESSED_ASTC_SRGB8_ALPHA8_6x5: //case COMPRESSED_ASTC_SRGB8_ALPHA8_6x6: //case COMPRESSED_ASTC_SRGB8_ALPHA8_8x5: //case COMPRESSED_ASTC_SRGB8_ALPHA8_8x6: //case COMPRESSED_ASTC_SRGB8_ALPHA8_8x8: // return _context.deviceFeatures.textureCompressionASTC_LDR == VK_TRUE; default: break; } return true;} void VKBackend::executeFrame(const FramePointer& frame) { using namespace vks::debugutils; // Initialize parts that cannot be initialized in default constructor. if (!_isInitialized) { initBeforeFirstFrame(); _isInitialized = true; } perFrameCleanup(); acquireFrameData(); for (const auto& batchPtr : frame->batches) { const auto& batch = *batchPtr; render(batch); } // Move pointer to current frame to property that will store it while it's being rendered and before it's recycled. _previouslyRenderedFrame = _currentlyRenderedFrame; _currentlyRenderedFrame = _currentFrame; bool reportBufferUpdates = false; if (_frameCounter % 20 == 0 && reportBufferUpdates) { qDebug() << "Buffer transfers: transfer pass: " << _currentFrame->_bufferTransferCounterTransferPass << " render pass: " << _currentFrame->_bufferTransferCounterRenderPass << " Batches " << frame->batches.size(); qDebug() << "Buffer transfers: uniform: " << _currentFrame->_uniformBufferTransferCounter << " vertex: " << _currentFrame->_vertexBufferTransferCounter << " index: " << _currentFrame->_indexBufferTransferCounter << " resource: " << _currentFrame->_resourceBufferTransferCounter << " created: " << _currentFrame->_bufferCreationCounter << " resized: " << _currentFrame->_bufferResizeCounter << " bytes: " << _currentFrame->_bufferTransferredBytes; } releaseFrameData(); _frameCounter++; _textureManagement._transferEngine->manageMemory(); // VKTODO: add a good way of toggling this /*if (_frameCounter % 4000 == 0) { dumpVmaMemoryStats(); }*/} void VKBackend::render(const Batch& batch) { using namespace vks::debugutils; const auto& commandBuffer = _currentCommandBuffer; _transform._skybox = _stereo._skybox = batch.isSkyboxEnabled(); // FIXME move this to between the transfer and draw passes, so that // framebuffer setup can see the proper stereo state and enable things // like foveation // Allow the batch to override the rendering stereo settings // for things like full framebuffer copy operations (deferred lighting passes) bool savedStereo = _stereo._enable; if (!batch.isStereoEnabled()) { _stereo._enable = false; } // Reset jitter _transform._projectionJitter._isEnabled = false; // VKTODO: for debugging, don't remove yet. /*if (batch.getName() == "SubsurfaceScattering::diffuseProfileGPU") { continue; }*/ cmdBeginLabel(commandBuffer, "batch:" + batch.getName(), glm::vec4{ 1, 1, 0, 1 }); { PROFILE_RANGE(gpu_vk_detail, "Transfer"); renderPassTransfer(batch); } //VKTODO/*#ifdef GPU_STEREO_DRAWCALL_INSTANCED if (_stereo.isStereo()) { glEnable(GL_CLIP_DISTANCE0); }#endif*/ { PROFILE_RANGE(gpu_vk_detail, _stereo._enable ? "Render Stereo" : "Render"); renderPassDraw(batch); } //VKTODO/*#ifdef GPU_STEREO_DRAWCALL_INSTANCED if (_stereo.isStereo()) { glDisable(GL_CLIP_DISTANCE0); }#endif*/ // VKTODO: This is a workaround to get image on screen for now. Later we need to look on how it's done on OpenGL branch and do it in a similar way. if (batch.getName() == "Resample::run") { _outputTexture = syncGPUObject(_cache.pipelineState.framebuffer); } if (batch.getName() == "CompositeHUD") { _outputTexture = syncGPUObject(_cache.pipelineState.framebuffer); } /*if (renderpassActive) { cmdEndLabel(commandBuffer); renderpassActive = false; }*/ // Restore the saved stereo state for the next batch _stereo._enable = savedStereo; if (batch._mustUpdatePreviousModels) { // Update object transform history for when the batch will be reexecuted for (auto& objectTransform : batch._objects) { objectTransform._previousModel = objectTransform._model; } batch._mustUpdatePreviousModels = false; } cmdEndLabel(commandBuffer);} void VKBackend::setDrawCommandBuffer(VkCommandBuffer commandBuffer) { _currentCommandBuffer = commandBuffer;} VkDescriptorImageInfo VKBackend::getDefaultTextureDescriptorInfo() { return _defaultTextureVk->getDescriptorImageInfo();} void VKBackend::TransformStageState::preUpdate(size_t commandIndex, const StereoState& stereo, const StereoState& prevStereo, Vec2u framebufferSize) { // Check all the dirty flags and update the state accordingly if (_invalidViewport) { _camera._viewport = glm::vec4(_viewport); } if (_invalidProj) { _camera._projection = _viewProjectionState._projection; } if (_invalidView) { // Apply the correction if (_viewProjectionState._viewIsCamera && ((_viewCorrectionEnabled || _viewCorrectionEnabledForFramePlayer) && _presentFrame.correction != glm::mat4())) { Transform::mult(_viewProjectionState._correctedView, _viewProjectionState._view, _presentFrame.correctionInverse); } else { _viewProjectionState._correctedView = _viewProjectionState._view; } // This is when the _view matrix gets assigned _viewProjectionState._correctedView.getInverseMatrix(_camera._view); } if (_invalidView || _invalidProj || _invalidViewport) { size_t offset = _cameraUboSize * _cameras.size(); _cameraOffsets.push_back(TransformStageState::Pair(commandIndex, offset)); pushCameraBufferElement(stereo, prevStereo, _cameras); if (_currentSavedTransformSlot != INVALID_SAVED_CAMERA_SLOT) { // Save the offset of the saved camera slot in the camera buffer. Can be used to copy // that data, or (in the future) to reuse the offset. _savedTransforms[_currentSavedTransformSlot]._cameraOffset = offset; } } // Flags are clean _invalidView = _invalidProj = _invalidViewport = false;} void VKBackend::TransformStageState::update(size_t commandIndex, const StereoState& stereo, VKBackend::UniformStageState &uniform, FrameData ¤tFrame) const { size_t offset = INVALID_OFFSET; while ((_camerasItr != _cameraOffsets.end()) && (commandIndex >= (*_camerasItr).first)) { offset = (*_camerasItr).second; _currentCameraOffset = offset; ++_camerasItr; } if (offset != INVALID_OFFSET) {#ifdef GPU_STEREO_CAMERA_BUFFER bindCurrentCamera(0, uniform, currentFrame);#else if (!stereo.isStereo()) { bindCurrentCamera(0); }#endif }} void VKBackend::TransformStageState::bindCurrentCamera(int eye, VKBackend::UniformStageState &uniform, FrameData ¤tFrame) const { if (_currentCameraOffset != INVALID_OFFSET) { static_assert(slot::buffer::Buffer::CameraTransform >= MAX_NUM_UNIFORM_BUFFERS, "TransformCamera may overlap pipeline uniform buffer slots. Invalidate uniform buffer slot cache for safety (call _uniform._buffers[TRANSFORM_CAMERA_SLOT].reset())."); // VKTODO: add convenience function for this? auto &buffer = uniform._buffers[slot::buffer::Buffer::CameraTransform]; Q_ASSERT(currentFrame._cameraBuffer); buffer.buffer = currentFrame._cameraBuffer.get(); buffer.size = sizeof(CameraBufferElement); buffer.offset = _currentCameraOffset + eye * _cameraUboSize; }} void VKBackend::TransformStageState::pushCameraBufferElement(const StereoState& stereo, const StereoState& prevStereo, TransformCameras& cameras) const { const float jitterAmplitude = _projectionJitter._scale; const Vec2 jitterScale = Vec2(jitterAmplitude * float(_projectionJitter._isEnabled & 1)) / Vec2(_viewport.z, _viewport.w); const Vec2 jitter = jitterScale * _projectionJitter._offset; if (stereo.isStereo()) {#ifdef GPU_STEREO_CAMERA_BUFFER cameras.push_back(CameraBufferElement(_camera.getEyeCamera(0, stereo, prevStereo, _viewProjectionState._correctedView, _viewProjectionState._previousCorrectedView, jitter), _camera.getEyeCamera(1, stereo, prevStereo, _viewProjectionState._correctedView, _viewProjectionState._previousCorrectedView, jitter)));#else cameras.push_back((_camera.getEyeCamera(0, stereo, prevStereo, _viewProjectionState._correctedView, _viewProjectionState._previousCorrectedView, jitter))); cameras.push_back((_camera.getEyeCamera(1, stereo, prevStereo, _viewProjectionState._correctedView, _viewProjectionState._previousCorrectedView, jitter)));#endif } else {#ifdef GPU_STEREO_CAMERA_BUFFER cameras.push_back(CameraBufferElement( _camera.getMonoCamera(_skybox, _viewProjectionState._correctedView, _viewProjectionState._previousCorrectedView, _viewProjectionState._previousProjection, jitter)));#else cameras.push_back((_camera.getMonoCamera(_skybox, _viewProjectionState._correctedView, _viewProjectionState._previousCorrectedView, _viewProjectionState._previousProjection, jitter)));#endif }} void VKBackend::preUpdateTransform() { // VKTODO: use proper viewport size or remove parameter _transform.preUpdate(_commandIndex, _stereo, _prevStereo, Vec2u(640, 480));} void VKBackend::store_glUniform1i(const Batch& batch, size_t paramOffset) { _currentFrame->addGlUniform(sizeof(int), reinterpret_cast<const void*>(&batch._params[paramOffset + 0]._int), _commandIndex);} void VKBackend::store_glUniform1f(const Batch& batch, size_t paramOffset) { _currentFrame->addGlUniform(sizeof(float), reinterpret_cast<const void*>(&batch._params[paramOffset + 0]._float), _commandIndex);} void VKBackend::store_glUniform2f(const Batch& batch, size_t paramOffset) { std::array<float, 2> array {batch._params[paramOffset + 1]._float, batch._params[paramOffset + 0]._float}; _currentFrame->addGlUniform(sizeof(float) * 2, reinterpret_cast<const void*>(array.data()), _commandIndex);} void VKBackend::store_glUniform3f(const Batch& batch, size_t paramOffset) { std::array<float, 3> array {batch._params[paramOffset + 2]._float, batch._params[paramOffset + 1]._float, batch._params[paramOffset + 0]._float}; _currentFrame->addGlUniform(sizeof(float) * 3, reinterpret_cast<const void*>(array.data()), _commandIndex); } void VKBackend::store_glUniform4f(const Batch& batch, size_t paramOffset) { std::array<float, 4> array {batch._params[paramOffset + 3]._float, batch._params[paramOffset + 2]._float, batch._params[paramOffset + 1]._float, batch._params[paramOffset + 0]._float}; _currentFrame->addGlUniform(sizeof(float) * 4, reinterpret_cast<const void*>(array.data()), _commandIndex);} void VKBackend::store_glUniform3fv(const Batch& batch, size_t paramOffset) { _currentFrame->addGlUniform(sizeof(float) * 3 * batch._params[paramOffset + 1]._uint, reinterpret_cast<const void*>(batch.readData(batch._params[paramOffset + 0]._uint)), _commandIndex);} Lines 1–500 of 3616. Load more
// // Created by Bradley Austin Davis on 2016/08/07 // Adapted for Vulkan in 2022-2025 by dr Karol Suprynowicz. // Copyright 2013-2018 High Fidelity, Inc. // Copyright 2023-2025 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // SPDX-License-Identifier: Apache-2.0 // #include "VKBackend.h" #include <mutex> #include <queue> #include <list> #include <functional> #include <glm/gtc/type_ptr.hpp> #include <QtCore/QProcessEnvironment> // For hash_combine #include <RegisteredMetaTypes.h> #include <gpu/TextureTable.h> #include <vk/Helpers.h> #include <vk/Version.h> #include <vk/Pipelines.h> #include "VKFramebuffer.h" #include "VKBuffer.h" #include "VKQuery.h" #include "VKForward.h" #include "VKShared.h" #include "VKTexture.h" #include "VKPipelineCache.h" #include <render-utils/ShaderConstants.h> #include "shared/FileUtils.h" #define FORCE_STRICT_TEXTURE 1 using namespace gpu; using namespace gpu::vk; // VKTODO: this is ugly solution Cache _cache; size_t VKBackend::UNIFORM_BUFFER_OFFSET_ALIGNMENT{ 4 }; static VKBackend* INSTANCE{ nullptr }; static const char* VK_BACKEND_PROPERTY_NAME = "com.highfidelity.vk.backend"; BackendPointer VKBackend::createBackend() { // FIXME provide a mechanism to override the backend for testing // Where the gpuContext is initialized and where the TRUE Backend is created and assigned std::shared_ptr<VKBackend> result = std::make_shared<VKBackend>(); INSTANCE = result.get(); void* voidInstance = &(*result); qApp->setProperty(VK_BACKEND_PROPERTY_NAME, QVariant::fromValue(voidInstance)); return result; } VKBackend& getBackend() { if (!INSTANCE) { INSTANCE = static_cast<VKBackend*>(qApp->property(VK_BACKEND_PROPERTY_NAME).value<void*>()); } return *INSTANCE; } void VKBackend::init() { } VKBackend::VKBackend() { if (!_context.instance) { #ifdef QT_NO_DEBUG _context.setValidationEnabled(false); // VKTODO: find nicer way of toggling this #else _context.setValidationEnabled(true); // VKTODO: find nicer way of toggling this #endif _context.createInstance(); _context.createDevice(); } { VkPipelineCacheCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; std::vector<uint8_t> pipelineCacheData; if (vks::util::loadPipelineCacheData(pipelineCacheData) && !pipelineCacheData.empty()) { createInfo.pInitialData = pipelineCacheData.data(); createInfo.initialDataSize = pipelineCacheData.size(); } vkCreatePipelineCache(_context.device->logicalDevice, &createInfo, nullptr, &_pipelineCache); } // Get the graphics queue qCDebug(gpu_vk_logging) << "VK Version: " << vks::Version(_context.device->properties.apiVersion).toString().c_str(); qCDebug(gpu_vk_logging) << "VK Driver: " << vks::Version(_context.device->properties.driverVersion).toString().c_str(); qCDebug(gpu_vk_logging) << "VK Vendor ID: " << _context.device->properties.vendorID; qCDebug(gpu_vk_logging) << "VK Device ID: " << _context.device->properties.deviceID; qCDebug(gpu_vk_logging) << "VK Device Name: " << _context.device->properties.deviceName; qCDebug(gpu_vk_logging) << "VK Device Type: " << _context.device->properties.deviceType; // Add frame data to frames pool for (int i = 0; i < 3; i++) { _framePool.push_back(std::make_shared<FrameData>(this)); _framesToReuse.push_back(_framePool.back()); } } VKBackend::~VKBackend() { Q_ASSERT(isBackendShutdownComplete); // FIXME queue up all the trash calls _context.destroyContext(); } void VKBackend::shutdown() { VK_CHECK_RESULT(vkQueueWaitIdle(_context.graphicsQueue)); VK_CHECK_RESULT(vkQueueWaitIdle(_context.transferQueue) ); VK_CHECK_RESULT(vkDeviceWaitIdle(_context.device->logicalDevice)); killTextureManagementStage(); _context.shutdownWindows(); // Release frames so their destructors can do a cleanup _currentFrame.reset(); _framesToReuse.resize(0); _framePool.resize(0); for (auto &module : _cache.moduleMap) { _context.recycler.trashVkShaderModule(module.second); } _cache.moduleMap.clear(); for (auto &renderPass : _cache.pipelineState._renderPassMap) { _context.recycler.trashVkRenderPass(renderPass.second); } _cache.pipelineState._renderPassMap.clear(); for (auto &pipeline : _cache.pipelineMap) { _context.recycler.trashVkDescriptorSetLayout(pipeline.second.uniformLayout); _context.recycler.trashVkDescriptorSetLayout(pipeline.second.storageLayout); _context.recycler.trashVkDescriptorSetLayout(pipeline.second.textureLayout); _context.recycler.trashVkPipelineLayout(pipeline.second.pipelineLayout); _context.recycler.trashVkPipeline(pipeline.second.pipeline); } _cache.pipelineMap.clear(); beforeShutdownCleanup(); { size_t pipelineCacheDataSize; VK_CHECK_RESULT(vkGetPipelineCacheData(_context.device->logicalDevice, _pipelineCache, &pipelineCacheDataSize, nullptr)); std::vector<uint8_t> pipelineCacheData; pipelineCacheData.resize(pipelineCacheDataSize); VK_CHECK_RESULT(vkGetPipelineCacheData(_context.device->logicalDevice, _pipelineCache, &pipelineCacheDataSize, nullptr)); if (!pipelineCacheData.empty()) { vks::util::savePipelineCacheData(pipelineCacheData); } } vkDestroyPipelineCache(_context.device->logicalDevice, _pipelineCache, nullptr); vmaDestroyAllocator(vks::Allocation::getAllocator()); isBackendShutdownComplete = true; } const std::string& VKBackend::getVersion() const { static const std::string VERSION{ "VK1.1" }; return VERSION; } bool VKBackend::isTextureManagementSparseEnabled() const { return _context.device->features.sparseResidencyImage2D == VK_TRUE; } bool VKBackend::supportedTextureFormat(const gpu::Element& format) const { switch (format.getSemantic()) { case COMPRESSED_BC1_SRGB: case COMPRESSED_BC1_SRGBA: case COMPRESSED_BC3_SRGBA: case COMPRESSED_BC4_RED: case COMPRESSED_BC5_XY: case COMPRESSED_BC6_RGB: case COMPRESSED_BC7_SRGBA: return _context.device->features.textureCompressionBC == VK_TRUE; case COMPRESSED_ETC2_RGB: case COMPRESSED_ETC2_SRGB: case COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA: case COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA: case COMPRESSED_ETC2_RGBA: case COMPRESSED_ETC2_SRGBA: case COMPRESSED_EAC_RED: case COMPRESSED_EAC_RED_SIGNED: case COMPRESSED_EAC_XY: case COMPRESSED_EAC_XY_SIGNED: return _context.device->features.textureCompressionETC2 == VK_TRUE; // VKTODO: //case COMPRESSED_ASTC_RGBA_10x10: //case COMPRESSED_ASTC_RGBA_10x5: //case COMPRESSED_ASTC_RGBA_10x6: //case COMPRESSED_ASTC_RGBA_10x8: //case COMPRESSED_ASTC_RGBA_12x10: //case COMPRESSED_ASTC_RGBA_12x12: //case COMPRESSED_ASTC_RGBA_4x4: //case COMPRESSED_ASTC_RGBA_5x4: //case COMPRESSED_ASTC_RGBA_5x5: //case COMPRESSED_ASTC_RGBA_6x5: //case COMPRESSED_ASTC_RGBA_6x6: //case COMPRESSED_ASTC_RGBA_8x5: //case COMPRESSED_ASTC_RGBA_8x6: //case COMPRESSED_ASTC_RGBA_8x8: //case COMPRESSED_ASTC_SRGB8_ALPHA8_10x10: //case COMPRESSED_ASTC_SRGB8_ALPHA8_10x5: //case COMPRESSED_ASTC_SRGB8_ALPHA8_10x6: //case COMPRESSED_ASTC_SRGB8_ALPHA8_10x8: //case COMPRESSED_ASTC_SRGB8_ALPHA8_12x10: //case COMPRESSED_ASTC_SRGB8_ALPHA8_12x12: //case COMPRESSED_ASTC_SRGB8_ALPHA8_4x4: //case COMPRESSED_ASTC_SRGB8_ALPHA8_5x4: //case COMPRESSED_ASTC_SRGB8_ALPHA8_5x5: //case COMPRESSED_ASTC_SRGB8_ALPHA8_6x5: //case COMPRESSED_ASTC_SRGB8_ALPHA8_6x6: //case COMPRESSED_ASTC_SRGB8_ALPHA8_8x5: //case COMPRESSED_ASTC_SRGB8_ALPHA8_8x6: //case COMPRESSED_ASTC_SRGB8_ALPHA8_8x8: // return _context.deviceFeatures.textureCompressionASTC_LDR == VK_TRUE; default: break; } return true; } void VKBackend::executeFrame(const FramePointer& frame) { using namespace vks::debugutils; // Initialize parts that cannot be initialized in default constructor. if (!_isInitialized) { initBeforeFirstFrame(); _isInitialized = true; } perFrameCleanup(); acquireFrameData(); for (const auto& batchPtr : frame->batches) { const auto& batch = *batchPtr; render(batch); } // Move pointer to current frame to property that will store it while it's being rendered and before it's recycled. _previouslyRenderedFrame = _currentlyRenderedFrame; _currentlyRenderedFrame = _currentFrame; bool reportBufferUpdates = false; if (_frameCounter % 20 == 0 && reportBufferUpdates) { qDebug() << "Buffer transfers: transfer pass: " << _currentFrame->_bufferTransferCounterTransferPass << " render pass: " << _currentFrame->_bufferTransferCounterRenderPass << " Batches " << frame->batches.size(); qDebug() << "Buffer transfers: uniform: " << _currentFrame->_uniformBufferTransferCounter << " vertex: " << _currentFrame->_vertexBufferTransferCounter << " index: " << _currentFrame->_indexBufferTransferCounter << " resource: " << _currentFrame->_resourceBufferTransferCounter << " created: " << _currentFrame->_bufferCreationCounter << " resized: " << _currentFrame->_bufferResizeCounter << " bytes: " << _currentFrame->_bufferTransferredBytes; } releaseFrameData(); _frameCounter++; _textureManagement._transferEngine->manageMemory(); // VKTODO: add a good way of toggling this /*if (_frameCounter % 4000 == 0) { dumpVmaMemoryStats(); }*/ } void VKBackend::render(const Batch& batch) { using namespace vks::debugutils; const auto& commandBuffer = _currentCommandBuffer; _transform._skybox = _stereo._skybox = batch.isSkyboxEnabled(); // FIXME move this to between the transfer and draw passes, so that // framebuffer setup can see the proper stereo state and enable things // like foveation // Allow the batch to override the rendering stereo settings // for things like full framebuffer copy operations (deferred lighting passes) bool savedStereo = _stereo._enable; if (!batch.isStereoEnabled()) { _stereo._enable = false; } // Reset jitter _transform._projectionJitter._isEnabled = false; // VKTODO: for debugging, don't remove yet. /*if (batch.getName() == "SubsurfaceScattering::diffuseProfileGPU") { continue; }*/ cmdBeginLabel(commandBuffer, "batch:" + batch.getName(), glm::vec4{ 1, 1, 0, 1 }); { PROFILE_RANGE(gpu_vk_detail, "Transfer"); renderPassTransfer(batch); } //VKTODO /*#ifdef GPU_STEREO_DRAWCALL_INSTANCED if (_stereo.isStereo()) { glEnable(GL_CLIP_DISTANCE0); } #endif*/ { PROFILE_RANGE(gpu_vk_detail, _stereo._enable ? "Render Stereo" : "Render"); renderPassDraw(batch); } //VKTODO /*#ifdef GPU_STEREO_DRAWCALL_INSTANCED if (_stereo.isStereo()) { glDisable(GL_CLIP_DISTANCE0); } #endif*/ // VKTODO: This is a workaround to get image on screen for now. Later we need to look on how it's done on OpenGL branch and do it in a similar way. if (batch.getName() == "Resample::run") { _outputTexture = syncGPUObject(_cache.pipelineState.framebuffer); } if (batch.getName() == "CompositeHUD") { _outputTexture = syncGPUObject(_cache.pipelineState.framebuffer); } /*if (renderpassActive) { cmdEndLabel(commandBuffer); renderpassActive = false; }*/ // Restore the saved stereo state for the next batch _stereo._enable = savedStereo; if (batch._mustUpdatePreviousModels) { // Update object transform history for when the batch will be reexecuted for (auto& objectTransform : batch._objects) { objectTransform._previousModel = objectTransform._model; } batch._mustUpdatePreviousModels = false; } cmdEndLabel(commandBuffer); } void VKBackend::setDrawCommandBuffer(VkCommandBuffer commandBuffer) { _currentCommandBuffer = commandBuffer; } VkDescriptorImageInfo VKBackend::getDefaultTextureDescriptorInfo() { return _defaultTextureVk->getDescriptorImageInfo(); } void VKBackend::TransformStageState::preUpdate(size_t commandIndex, const StereoState& stereo, const StereoState& prevStereo, Vec2u framebufferSize) { // Check all the dirty flags and update the state accordingly if (_invalidViewport) { _camera._viewport = glm::vec4(_viewport); } if (_invalidProj) { _camera._projection = _viewProjectionState._projection; } if (_invalidView) { // Apply the correction if (_viewProjectionState._viewIsCamera && ((_viewCorrectionEnabled || _viewCorrectionEnabledForFramePlayer) && _presentFrame.correction != glm::mat4())) { Transform::mult(_viewProjectionState._correctedView, _viewProjectionState._view, _presentFrame.correctionInverse); } else { _viewProjectionState._correctedView = _viewProjectionState._view; } // This is when the _view matrix gets assigned _viewProjectionState._correctedView.getInverseMatrix(_camera._view); } if (_invalidView || _invalidProj || _invalidViewport) { size_t offset = _cameraUboSize * _cameras.size(); _cameraOffsets.push_back(TransformStageState::Pair(commandIndex, offset)); pushCameraBufferElement(stereo, prevStereo, _cameras); if (_currentSavedTransformSlot != INVALID_SAVED_CAMERA_SLOT) { // Save the offset of the saved camera slot in the camera buffer. Can be used to copy // that data, or (in the future) to reuse the offset. _savedTransforms[_currentSavedTransformSlot]._cameraOffset = offset; } } // Flags are clean _invalidView = _invalidProj = _invalidViewport = false; } void VKBackend::TransformStageState::update(size_t commandIndex, const StereoState& stereo, VKBackend::UniformStageState &uniform, FrameData ¤tFrame) const { size_t offset = INVALID_OFFSET; while ((_camerasItr != _cameraOffsets.end()) && (commandIndex >= (*_camerasItr).first)) { offset = (*_camerasItr).second; _currentCameraOffset = offset; ++_camerasItr; } if (offset != INVALID_OFFSET) { #ifdef GPU_STEREO_CAMERA_BUFFER bindCurrentCamera(0, uniform, currentFrame); #else if (!stereo.isStereo()) { bindCurrentCamera(0); } #endif } } void VKBackend::TransformStageState::bindCurrentCamera(int eye, VKBackend::UniformStageState &uniform, FrameData ¤tFrame) const { if (_currentCameraOffset != INVALID_OFFSET) { static_assert(slot::buffer::Buffer::CameraTransform >= MAX_NUM_UNIFORM_BUFFERS, "TransformCamera may overlap pipeline uniform buffer slots. Invalidate uniform buffer slot cache for safety (call _uniform._buffers[TRANSFORM_CAMERA_SLOT].reset())."); // VKTODO: add convenience function for this? auto &buffer = uniform._buffers[slot::buffer::Buffer::CameraTransform]; Q_ASSERT(currentFrame._cameraBuffer); buffer.buffer = currentFrame._cameraBuffer.get(); buffer.size = sizeof(CameraBufferElement); buffer.offset = _currentCameraOffset + eye * _cameraUboSize; } } void VKBackend::TransformStageState::pushCameraBufferElement(const StereoState& stereo, const StereoState& prevStereo, TransformCameras& cameras) const { const float jitterAmplitude = _projectionJitter._scale; const Vec2 jitterScale = Vec2(jitterAmplitude * float(_projectionJitter._isEnabled & 1)) / Vec2(_viewport.z, _viewport.w); const Vec2 jitter = jitterScale * _projectionJitter._offset; if (stereo.isStereo()) { #ifdef GPU_STEREO_CAMERA_BUFFER cameras.push_back(CameraBufferElement(_camera.getEyeCamera(0, stereo, prevStereo, _viewProjectionState._correctedView, _viewProjectionState._previousCorrectedView, jitter), _camera.getEyeCamera(1, stereo, prevStereo, _viewProjectionState._correctedView, _viewProjectionState._previousCorrectedView, jitter))); #else cameras.push_back((_camera.getEyeCamera(0, stereo, prevStereo, _viewProjectionState._correctedView, _viewProjectionState._previousCorrectedView, jitter))); cameras.push_back((_camera.getEyeCamera(1, stereo, prevStereo, _viewProjectionState._correctedView, _viewProjectionState._previousCorrectedView, jitter))); #endif } else { #ifdef GPU_STEREO_CAMERA_BUFFER cameras.push_back(CameraBufferElement( _camera.getMonoCamera(_skybox, _viewProjectionState._correctedView, _viewProjectionState._previousCorrectedView, _viewProjectionState._previousProjection, jitter))); #else cameras.push_back((_camera.getMonoCamera(_skybox, _viewProjectionState._correctedView, _viewProjectionState._previousCorrectedView, _viewProjectionState._previousProjection, jitter))); #endif } } void VKBackend::preUpdateTransform() { // VKTODO: use proper viewport size or remove parameter _transform.preUpdate(_commandIndex, _stereo, _prevStereo, Vec2u(640, 480)); } void VKBackend::store_glUniform1i(const Batch& batch, size_t paramOffset) { _currentFrame->addGlUniform(sizeof(int), reinterpret_cast<const void*>(&batch._params[paramOffset + 0]._int), _commandIndex); } void VKBackend::store_glUniform1f(const Batch& batch, size_t paramOffset) { _currentFrame->addGlUniform(sizeof(float), reinterpret_cast<const void*>(&batch._params[paramOffset + 0]._float), _commandIndex); } void VKBackend::store_glUniform2f(const Batch& batch, size_t paramOffset) { std::array<float, 2> array {batch._params[paramOffset + 1]._float, batch._params[paramOffset + 0]._float}; _currentFrame->addGlUniform(sizeof(float) * 2, reinterpret_cast<const void*>(array.data()), _commandIndex); } void VKBackend::store_glUniform3f(const Batch& batch, size_t paramOffset) { std::array<float, 3> array {batch._params[paramOffset + 2]._float, batch._params[paramOffset + 1]._float, batch._params[paramOffset + 0]._float}; _currentFrame->addGlUniform(sizeof(float) * 3, reinterpret_cast<const void*>(array.data()), _commandIndex); } void VKBackend::store_glUniform4f(const Batch& batch, size_t paramOffset) { std::array<float, 4> array {batch._params[paramOffset + 3]._float, batch._params[paramOffset + 2]._float, batch._params[paramOffset + 1]._float, batch._params[paramOffset + 0]._float}; _currentFrame->addGlUniform(sizeof(float) * 4, reinterpret_cast<const void*>(array.data()), _commandIndex); } void VKBackend::store_glUniform3fv(const Batch& batch, size_t paramOffset) { _currentFrame->addGlUniform(sizeof(float) * 3 * batch._params[paramOffset + 1]._uint, reinterpret_cast<const void*>(batch.readData(batch._params[paramOffset + 0]._uint)), _commandIndex); }