blob: 926de57e77986948a46314eb237a2f778f68e7e4 [file] [log] [blame]
// Copyright 2017 The NXT Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "backend/vulkan/VulkanBackend.h"
#include "backend/Commands.h"
#include "backend/vulkan/BufferVk.h"
#include "backend/vulkan/BufferUploader.h"
#include "common/Platform.h"
#include <spirv-cross/spirv_cross.hpp>
#include <iostream>
#if NXT_PLATFORM_LINUX
const char kVulkanLibName[] = "libvulkan.so.1";
#elif NXT_PLATFORM_WINDOWS
const char kVulkanLibName[] = "vulkan-1.dll";
#else
#error "Unimplemented Vulkan backend platform"
#endif
namespace backend {
namespace vulkan {
nxtProcTable GetNonValidatingProcs();
nxtProcTable GetValidatingProcs();
void Init(nxtProcTable* procs, nxtDevice* device) {
*procs = GetValidatingProcs();
*device = reinterpret_cast<nxtDevice>(new Device);
}
// Device
Device::Device() {
if (!vulkanLib.Open(kVulkanLibName)) {
ASSERT(false);
return;
}
VulkanFunctions* functions = GetMutableFunctions();
if (!functions->LoadGlobalProcs(vulkanLib)) {
ASSERT(false);
return;
}
if (!GatherGlobalInfo(*this, &globalInfo)) {
ASSERT(false);
return;
}
VulkanGlobalKnobs usedGlobalKnobs = {};
if (!CreateInstance(&usedGlobalKnobs)) {
ASSERT(false);
return;
}
*static_cast<VulkanGlobalKnobs*>(&globalInfo) = usedGlobalKnobs;
if (!functions->LoadInstanceProcs(instance, usedGlobalKnobs)) {
ASSERT(false);
return;
}
if (usedGlobalKnobs.debugReport) {
if (!RegisterDebugReport()) {
ASSERT(false);
return;
}
}
std::vector<VkPhysicalDevice> physicalDevices;
if (!GetPhysicalDevices(*this, &physicalDevices) || physicalDevices.empty()) {
ASSERT(false);
return;
}
// TODO(cwallez@chromium.org): Choose the physical device based on ???
physicalDevice = physicalDevices[0];
if (!GatherDeviceInfo(*this, physicalDevice, &deviceInfo)) {
ASSERT(false);
return;
}
VulkanDeviceKnobs usedDeviceKnobs = {};
if (!CreateDevice(&usedDeviceKnobs)) {
ASSERT(false);
return;
}
*static_cast<VulkanDeviceKnobs*>(&deviceInfo) = usedDeviceKnobs;
if (!functions->LoadDeviceProcs(vkDevice, usedDeviceKnobs)) {
ASSERT(false);
return;
}
GatherQueueFromDevice();
mapReadRequestTracker = new MapReadRequestTracker(this);
memoryAllocator = new MemoryAllocator(this);
bufferUploader = new BufferUploader(this);
}
Device::~Device() {
// Immediately forget about all pending commands so we don't try to submit them in Tick
FreeCommands(&pendingCommands);
if (fn.QueueWaitIdle(queue) != VK_SUCCESS) {
ASSERT(false);
}
CheckPassedFences();
ASSERT(fencesInFlight.empty());
// Some operations might have been started since the last submit and waiting
// on a serial that doesn't have a corresponding fence enqueued. Force all
// operations to look as if they were completed (because they were).
completedSerial = nextSerial;
Tick();
ASSERT(commandsInFlight.Empty());
for (auto& commands : unusedCommands) {
FreeCommands(&commands);
}
unusedCommands.clear();
for (VkFence fence : unusedFences) {
fn.DestroyFence(vkDevice, fence, nullptr);
}
unusedFences.clear();
if (bufferUploader) {
delete bufferUploader;
bufferUploader = nullptr;
}
if (memoryAllocator) {
delete memoryAllocator;
memoryAllocator = nullptr;
}
if (mapReadRequestTracker) {
delete mapReadRequestTracker;
mapReadRequestTracker = nullptr;
}
// VkQueues are destroyed when the VkDevice is destroyed
if (vkDevice != VK_NULL_HANDLE) {
fn.DestroyDevice(vkDevice, nullptr);
vkDevice = VK_NULL_HANDLE;
}
if (debugReportCallback != VK_NULL_HANDLE) {
fn.DestroyDebugReportCallbackEXT(instance, debugReportCallback, nullptr);
debugReportCallback = VK_NULL_HANDLE;
}
// VkPhysicalDevices are destroyed when the VkInstance is destroyed
if (instance != VK_NULL_HANDLE) {
fn.DestroyInstance(instance, nullptr);
instance = VK_NULL_HANDLE;
}
}
BindGroupBase* Device::CreateBindGroup(BindGroupBuilder* builder) {
return new BindGroup(builder);
}
BindGroupLayoutBase* Device::CreateBindGroupLayout(BindGroupLayoutBuilder* builder) {
return new BindGroupLayout(builder);
}
BlendStateBase* Device::CreateBlendState(BlendStateBuilder* builder) {
return new BlendState(builder);
}
BufferBase* Device::CreateBuffer(BufferBuilder* builder) {
return new Buffer(builder);
}
BufferViewBase* Device::CreateBufferView(BufferViewBuilder* builder) {
return new BufferView(builder);
}
CommandBufferBase* Device::CreateCommandBuffer(CommandBufferBuilder* builder) {
return new CommandBuffer(builder);
}
ComputePipelineBase* Device::CreateComputePipeline(ComputePipelineBuilder* builder) {
return new ComputePipeline(builder);
}
DepthStencilStateBase* Device::CreateDepthStencilState(DepthStencilStateBuilder* builder) {
return new DepthStencilState(builder);
}
FramebufferBase* Device::CreateFramebuffer(FramebufferBuilder* builder) {
return new Framebuffer(builder);
}
InputStateBase* Device::CreateInputState(InputStateBuilder* builder) {
return new InputState(builder);
}
PipelineLayoutBase* Device::CreatePipelineLayout(PipelineLayoutBuilder* builder) {
return new PipelineLayout(builder);
}
QueueBase* Device::CreateQueue(QueueBuilder* builder) {
return new Queue(builder);
}
RenderPassBase* Device::CreateRenderPass(RenderPassBuilder* builder) {
return new RenderPass(builder);
}
RenderPipelineBase* Device::CreateRenderPipeline(RenderPipelineBuilder* builder) {
return new RenderPipeline(builder);
}
SamplerBase* Device::CreateSampler(SamplerBuilder* builder) {
return new Sampler(builder);
}
ShaderModuleBase* Device::CreateShaderModule(ShaderModuleBuilder* builder) {
auto module = new ShaderModule(builder);
spirv_cross::Compiler compiler(builder->AcquireSpirv());
module->ExtractSpirvInfo(compiler);
return module;
}
SwapChainBase* Device::CreateSwapChain(SwapChainBuilder* builder) {
return new SwapChain(builder);
}
TextureBase* Device::CreateTexture(TextureBuilder* builder) {
return new Texture(builder);
}
TextureViewBase* Device::CreateTextureView(TextureViewBuilder* builder) {
return new TextureView(builder);
}
void Device::TickImpl() {
CheckPassedFences();
RecycleCompletedCommands();
mapReadRequestTracker->Tick(completedSerial);
bufferUploader->Tick(completedSerial);
memoryAllocator->Tick(completedSerial);
if (pendingCommands.pool != VK_NULL_HANDLE) {
SubmitPendingCommands();
}
}
const VulkanDeviceInfo& Device::GetDeviceInfo() const {
return deviceInfo;
}
MapReadRequestTracker* Device::GetMapReadRequestTracker() const {
return mapReadRequestTracker;
}
MemoryAllocator* Device::GetMemoryAllocator() const {
return memoryAllocator;
}
BufferUploader* Device::GetBufferUploader() const {
return bufferUploader;
}
Serial Device::GetSerial() const {
return nextSerial;
}
VkCommandBuffer Device::GetPendingCommandBuffer() {
if (pendingCommands.pool == VK_NULL_HANDLE) {
pendingCommands = GetUnusedCommands();
VkCommandBufferBeginInfo beginInfo;
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.pNext = nullptr;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
beginInfo.pInheritanceInfo = nullptr;
if (fn.BeginCommandBuffer(pendingCommands.commandBuffer, &beginInfo) != VK_SUCCESS) {
ASSERT(false);
}
}
return pendingCommands.commandBuffer;
}
void Device::SubmitPendingCommands() {
if (pendingCommands.pool == VK_NULL_HANDLE) {
return;
}
if (fn.EndCommandBuffer(pendingCommands.commandBuffer) != VK_SUCCESS) {
ASSERT(false);
}
VkSubmitInfo submitInfo;
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.pNext = nullptr;
submitInfo.waitSemaphoreCount = 0;
submitInfo.pWaitSemaphores = nullptr;
submitInfo.pWaitDstStageMask = 0;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &pendingCommands.commandBuffer;
submitInfo.signalSemaphoreCount = 0;
submitInfo.pSignalSemaphores = 0;
VkFence fence = GetUnusedFence();
if (fn.QueueSubmit(queue, 1, &submitInfo, fence) != VK_SUCCESS) {
ASSERT(false);
}
commandsInFlight.Enqueue(pendingCommands, nextSerial);
pendingCommands = CommandPoolAndBuffer();
fencesInFlight.emplace(fence, nextSerial);
nextSerial++;
}
VkInstance Device::GetInstance() const {
return instance;
}
VkDevice Device::GetVkDevice() const {
return vkDevice;
}
bool Device::CreateInstance(VulkanGlobalKnobs* usedKnobs) {
std::vector<const char*> layersToRequest;
std::vector<const char*> extensionsToRequest;
#if defined(NXT_ENABLE_ASSERTS)
if (globalInfo.standardValidation) {
layersToRequest.push_back(kLayerNameLunargStandardValidation);
usedKnobs->standardValidation = true;
}
if (globalInfo.debugReport) {
extensionsToRequest.push_back(kExtensionNameExtDebugReport);
usedKnobs->debugReport = true;
}
#endif
VkApplicationInfo appInfo;
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
appInfo.pNext = nullptr;
appInfo.pApplicationName = nullptr;
appInfo.applicationVersion = 0;
appInfo.pEngineName = nullptr;
appInfo.engineVersion = 0;
appInfo.apiVersion = VK_API_VERSION_1_0;
VkInstanceCreateInfo createInfo;
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pNext = nullptr;
createInfo.flags = 0;
createInfo.pApplicationInfo = &appInfo;
createInfo.enabledLayerCount = static_cast<uint32_t>(layersToRequest.size());
createInfo.ppEnabledLayerNames = layersToRequest.data();
createInfo.enabledExtensionCount = static_cast<uint32_t>(extensionsToRequest.size());
createInfo.ppEnabledExtensionNames = extensionsToRequest.data();
if (fn.CreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
return false;
}
return true;
}
bool Device::CreateDevice(VulkanDeviceKnobs* usedKnobs) {
float zero = 0.0f;
std::vector<const char*> layersToRequest;
std::vector<const char*> extensionsToRequest;
std::vector<VkDeviceQueueCreateInfo> queuesToRequest;
if (deviceInfo.swapchain) {
extensionsToRequest.push_back(kExtensionNameKhrSwapchain);
usedKnobs->swapchain = true;
}
// Find a universal queue family
{
constexpr uint32_t kUniversalFlags = VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT;
int universalQueueFamily = -1;
for (unsigned int i = 0; i < deviceInfo.queueFamilies.size(); ++i) {
if ((deviceInfo.queueFamilies[i].queueFlags & kUniversalFlags) == kUniversalFlags) {
universalQueueFamily = i;
break;
}
}
if (universalQueueFamily == -1) {
return false;
}
queueFamily = static_cast<uint32_t>(universalQueueFamily);
}
// Choose to create a single universal queue
{
VkDeviceQueueCreateInfo queueCreateInfo;
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.pNext = nullptr;
queueCreateInfo.flags = 0;
queueCreateInfo.queueFamilyIndex = static_cast<uint32_t>(queueFamily);
queueCreateInfo.queueCount = 1;
queueCreateInfo.pQueuePriorities = &zero;
queuesToRequest.push_back(queueCreateInfo);
}
VkDeviceCreateInfo createInfo;
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
createInfo.pNext = nullptr;
createInfo.flags = 0;
createInfo.queueCreateInfoCount = static_cast<uint32_t>(queuesToRequest.size());
createInfo.pQueueCreateInfos = queuesToRequest.data();
createInfo.enabledLayerCount = static_cast<uint32_t>(layersToRequest.size());
createInfo.ppEnabledLayerNames = layersToRequest.data();
createInfo.enabledExtensionCount = static_cast<uint32_t>(extensionsToRequest.size());
createInfo.ppEnabledExtensionNames = extensionsToRequest.data();
createInfo.pEnabledFeatures = &usedKnobs->features;
if (fn.CreateDevice(physicalDevice, &createInfo, nullptr, &vkDevice) != VK_SUCCESS) {
return false;
}
return true;
}
void Device::GatherQueueFromDevice() {
fn.GetDeviceQueue(vkDevice, queueFamily, 0, &queue);
}
bool Device::RegisterDebugReport() {
VkDebugReportCallbackCreateInfoEXT createInfo;
createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
createInfo.pNext = nullptr;
createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT;
createInfo.pfnCallback = Device::OnDebugReportCallback;
createInfo.pUserData = this;
if (fn.CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &debugReportCallback) != VK_SUCCESS) {
return false;
}
return true;
}
VkBool32 Device::OnDebugReportCallback(VkDebugReportFlagsEXT flags,
VkDebugReportObjectTypeEXT /*objectType*/,
uint64_t /*object*/,
size_t /*location*/,
int32_t /*messageCode*/,
const char* /*pLayerPrefix*/,
const char* pMessage,
void* /*pUserdata*/) {
std::cout << pMessage << std::endl;
ASSERT((flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) == 0);
return VK_FALSE;
}
VulkanFunctions* Device::GetMutableFunctions() {
return const_cast<VulkanFunctions*>(&fn);
}
VkFence Device::GetUnusedFence() {
if (!unusedFences.empty()) {
VkFence fence = unusedFences.back();
unusedFences.pop_back();
return fence;
}
VkFenceCreateInfo createInfo;
createInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
createInfo.pNext = nullptr;
createInfo.flags = 0;
VkFence fence = VK_NULL_HANDLE;
if (fn.CreateFence(vkDevice, &createInfo, nullptr, &fence) != VK_SUCCESS) {
ASSERT(false);
}
return fence;
}
void Device::CheckPassedFences() {
while (!fencesInFlight.empty()) {
VkFence fence = fencesInFlight.front().first;
Serial fenceSerial = fencesInFlight.front().second;
VkResult result = fn.GetFenceStatus(vkDevice, fence);
ASSERT(result == VK_SUCCESS || result == VK_NOT_READY);
// Fence are added in order, so we can stop searching as soon
// as we see one that's not ready.
if (result == VK_NOT_READY) {
return;
}
if (fn.ResetFences(vkDevice, 1, &fence) != VK_SUCCESS) {
ASSERT(false);
}
unusedFences.push_back(fence);
fencesInFlight.pop();
ASSERT(fenceSerial > completedSerial);
completedSerial = fenceSerial;
}
}
Device::CommandPoolAndBuffer Device::GetUnusedCommands() {
if (!unusedCommands.empty()) {
CommandPoolAndBuffer commands = unusedCommands.back();
unusedCommands.pop_back();
return commands;
}
CommandPoolAndBuffer commands;
VkCommandPoolCreateInfo createInfo;
createInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
createInfo.pNext = nullptr;
createInfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
createInfo.queueFamilyIndex = queueFamily;
if (fn.CreateCommandPool(vkDevice, &createInfo, nullptr, &commands.pool) != VK_SUCCESS) {
ASSERT(false);
}
VkCommandBufferAllocateInfo allocateInfo;
allocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocateInfo.pNext = nullptr;
allocateInfo.commandPool = commands.pool;
allocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocateInfo.commandBufferCount = 1;
if (fn.AllocateCommandBuffers(vkDevice, &allocateInfo, &commands.commandBuffer) != VK_SUCCESS) {
ASSERT(false);
}
return commands;
}
void Device::RecycleCompletedCommands() {
for (auto& commands : commandsInFlight.IterateUpTo(completedSerial)) {
if (fn.ResetCommandPool(vkDevice, commands.pool, 0) != VK_SUCCESS) {
ASSERT(false);
}
unusedCommands.push_back(commands);
}
commandsInFlight.ClearUpTo(completedSerial);
}
void Device::FreeCommands(CommandPoolAndBuffer* commands) {
if (commands->pool != VK_NULL_HANDLE) {
fn.DestroyCommandPool(vkDevice, commands->pool, nullptr);
commands->pool = VK_NULL_HANDLE;
}
// Command buffers are implicitly destroyed when the command pool is.
commands->commandBuffer = VK_NULL_HANDLE;
}
// Queue
Queue::Queue(QueueBuilder* builder)
: QueueBase(builder) {
}
Queue::~Queue() {
}
void Queue::Submit(uint32_t, CommandBuffer* const*) {
}
// Texture
Texture::Texture(TextureBuilder* builder)
: TextureBase(builder) {
}
Texture::~Texture() {
}
void Texture::TransitionUsageImpl(nxt::TextureUsageBit, nxt::TextureUsageBit) {
}
// SwapChain
SwapChain::SwapChain(SwapChainBuilder* builder)
: SwapChainBase(builder) {
const auto& im = GetImplementation();
im.Init(im.userData, nullptr);
}
SwapChain::~SwapChain() {
}
TextureBase* SwapChain::GetNextTextureImpl(TextureBuilder* builder) {
return GetDevice()->CreateTexture(builder);
}
}
}