mirror of
https://github.com/doitsujin/dxvk.git
synced 2024-12-14 00:48:44 +01:00
7fa26f1c87
Spilling the render pass should make shader storage buffer/image writes visible due to how external subpass dependencies are defined. For UAV rendering, we need to do this when changing the UAVs, even if the render targets themselves do not change.
469 lines
17 KiB
C++
469 lines
17 KiB
C++
#include "dxgi_presenter.h"
|
|
|
|
#include "../spirv/spirv_module.h"
|
|
|
|
#include <dxgi_presenter_frag.h>
|
|
#include <dxgi_presenter_vert.h>
|
|
|
|
namespace dxvk {
|
|
|
|
DxgiVkPresenter::DxgiVkPresenter(
|
|
const Rc<DxvkDevice>& device,
|
|
HWND window)
|
|
: m_window (window),
|
|
m_device (device),
|
|
m_context (device->createContext()) {
|
|
|
|
// Some games don't work with deferred surface creation,
|
|
// so we should default to initializing it immediately.
|
|
DxgiOptions dxgiOptions = getDxgiAppOptions(env::getExeName());
|
|
|
|
if (!dxgiOptions.test(DxgiOption::DeferSurfaceCreation))
|
|
m_surface = CreateSurface();
|
|
|
|
// Reset options for the swap chain itself. We will
|
|
// create a swap chain object before presentation.
|
|
m_options.preferredSurfaceFormat = { VK_FORMAT_UNDEFINED, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
|
|
m_options.preferredPresentMode = VK_PRESENT_MODE_FIFO_KHR;
|
|
m_options.preferredBufferSize = { 0u, 0u };
|
|
|
|
// Samplers for presentation. We'll create one with point sampling that will
|
|
// be used when the back buffer resolution matches the output resolution, and
|
|
// one with linar sampling that will be used when the image will be scaled.
|
|
m_samplerFitting = CreateSampler(VK_FILTER_NEAREST, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER);
|
|
m_samplerScaling = CreateSampler(VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER);
|
|
|
|
// Create objects required for the gamma ramp. This is implemented partially
|
|
// with an UBO, which stores global parameters, and a lookup texture, which
|
|
// stores the actual gamma ramp and can be sampled with a linear filter.
|
|
m_gammaSampler = CreateSampler(VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE);
|
|
m_gammaTexture = CreateGammaTexture();
|
|
m_gammaTextureView = CreateGammaTextureView();
|
|
|
|
// Set up context state. The shader bindings and the
|
|
// constant state objects will never be modified.
|
|
DxvkInputAssemblyState iaState;
|
|
iaState.primitiveTopology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
|
|
iaState.primitiveRestart = VK_FALSE;
|
|
iaState.patchVertexCount = 0;
|
|
m_context->setInputAssemblyState(iaState);
|
|
|
|
m_context->setInputLayout(
|
|
0, nullptr, 0, nullptr);
|
|
|
|
DxvkRasterizerState rsState;
|
|
rsState.enableDepthClamp = VK_FALSE;
|
|
rsState.enableDiscard = VK_FALSE;
|
|
rsState.polygonMode = VK_POLYGON_MODE_FILL;
|
|
rsState.cullMode = VK_CULL_MODE_BACK_BIT;
|
|
rsState.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
|
rsState.depthBiasEnable = VK_FALSE;
|
|
rsState.depthBiasConstant = 0.0f;
|
|
rsState.depthBiasClamp = 0.0f;
|
|
rsState.depthBiasSlope = 0.0f;
|
|
m_context->setRasterizerState(rsState);
|
|
|
|
DxvkMultisampleState msState;
|
|
msState.sampleMask = 0xffffffff;
|
|
msState.enableAlphaToCoverage = VK_FALSE;
|
|
msState.enableAlphaToOne = VK_FALSE;
|
|
m_context->setMultisampleState(msState);
|
|
|
|
VkStencilOpState stencilOp;
|
|
stencilOp.failOp = VK_STENCIL_OP_KEEP;
|
|
stencilOp.passOp = VK_STENCIL_OP_KEEP;
|
|
stencilOp.depthFailOp = VK_STENCIL_OP_KEEP;
|
|
stencilOp.compareOp = VK_COMPARE_OP_ALWAYS;
|
|
stencilOp.compareMask = 0xFFFFFFFF;
|
|
stencilOp.writeMask = 0xFFFFFFFF;
|
|
stencilOp.reference = 0;
|
|
|
|
DxvkDepthStencilState dsState;
|
|
dsState.enableDepthTest = VK_FALSE;
|
|
dsState.enableDepthWrite = VK_FALSE;
|
|
dsState.enableStencilTest = VK_FALSE;
|
|
dsState.depthCompareOp = VK_COMPARE_OP_ALWAYS;
|
|
dsState.stencilOpFront = stencilOp;
|
|
dsState.stencilOpBack = stencilOp;
|
|
m_context->setDepthStencilState(dsState);
|
|
|
|
DxvkLogicOpState loState;
|
|
loState.enableLogicOp = VK_FALSE;
|
|
loState.logicOp = VK_LOGIC_OP_NO_OP;
|
|
m_context->setLogicOpState(loState);
|
|
|
|
m_blendMode.enableBlending = VK_FALSE;
|
|
m_blendMode.colorSrcFactor = VK_BLEND_FACTOR_ONE;
|
|
m_blendMode.colorDstFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
|
m_blendMode.colorBlendOp = VK_BLEND_OP_ADD;
|
|
m_blendMode.alphaSrcFactor = VK_BLEND_FACTOR_ONE;
|
|
m_blendMode.alphaDstFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
|
m_blendMode.alphaBlendOp = VK_BLEND_OP_ADD;
|
|
m_blendMode.writeMask = VK_COLOR_COMPONENT_R_BIT
|
|
| VK_COLOR_COMPONENT_G_BIT
|
|
| VK_COLOR_COMPONENT_B_BIT
|
|
| VK_COLOR_COMPONENT_A_BIT;
|
|
|
|
m_context->bindShader(
|
|
VK_SHADER_STAGE_VERTEX_BIT,
|
|
CreateVertexShader());
|
|
|
|
m_context->bindShader(
|
|
VK_SHADER_STAGE_FRAGMENT_BIT,
|
|
CreateFragmentShader());
|
|
|
|
m_hud = hud::Hud::createHud(m_device);
|
|
}
|
|
|
|
|
|
DxgiVkPresenter::~DxgiVkPresenter() {
|
|
m_device->waitForIdle();
|
|
}
|
|
|
|
|
|
void DxgiVkPresenter::InitBackBuffer(const Rc<DxvkImage>& Image) {
|
|
m_context->beginRecording(
|
|
m_device->createCommandList());
|
|
|
|
VkImageSubresourceRange sr;
|
|
sr.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
sr.baseMipLevel = 0;
|
|
sr.levelCount = Image->info().mipLevels;
|
|
sr.baseArrayLayer = 0;
|
|
sr.layerCount = Image->info().numLayers;
|
|
|
|
m_context->initImage(Image, sr);
|
|
|
|
m_device->submitCommandList(
|
|
m_context->endRecording(),
|
|
nullptr, nullptr);
|
|
}
|
|
|
|
|
|
void DxgiVkPresenter::PresentImage(UINT SyncInterval) {
|
|
if (m_hud != nullptr) {
|
|
m_hud->render({
|
|
m_options.preferredBufferSize.width,
|
|
m_options.preferredBufferSize.height,
|
|
});
|
|
}
|
|
|
|
// Check whether the back buffer size is the same
|
|
// as the window size, in which case we should use
|
|
// VK_FILTER_NEAREST to avoid blurry output
|
|
const bool fitSize =
|
|
m_backBuffer->info().extent.width == m_options.preferredBufferSize.width
|
|
&& m_backBuffer->info().extent.height == m_options.preferredBufferSize.height;
|
|
|
|
for (uint32_t i = 0; i < SyncInterval || i < 1; i++) {
|
|
m_context->beginRecording(
|
|
m_device->createCommandList());
|
|
|
|
// Resolve back buffer if it is multisampled. We
|
|
// only have to do it only for the first frame.
|
|
if (m_backBufferResolve != nullptr && i == 0) {
|
|
VkImageSubresourceLayers resolveSubresources;
|
|
resolveSubresources.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
resolveSubresources.mipLevel = 0;
|
|
resolveSubresources.baseArrayLayer = 0;
|
|
resolveSubresources.layerCount = 1;
|
|
|
|
m_context->resolveImage(
|
|
m_backBufferResolve, resolveSubresources,
|
|
m_backBuffer, resolveSubresources,
|
|
VK_FORMAT_UNDEFINED);
|
|
}
|
|
|
|
auto swapSemas = m_swapchain->getSemaphorePair();
|
|
auto swapImage = m_swapchain->getImageView(swapSemas.acquireSync);
|
|
|
|
DxvkRenderTargets renderTargets;
|
|
renderTargets.color[0].view = swapImage;
|
|
renderTargets.color[0].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
m_context->bindRenderTargets(renderTargets, false);
|
|
|
|
VkViewport viewport;
|
|
viewport.x = 0.0f;
|
|
viewport.y = 0.0f;
|
|
viewport.width = float(swapImage->imageInfo().extent.width);
|
|
viewport.height = float(swapImage->imageInfo().extent.height);
|
|
viewport.minDepth = 0.0f;
|
|
viewport.maxDepth = 1.0f;
|
|
|
|
VkRect2D scissor;
|
|
scissor.offset.x = 0;
|
|
scissor.offset.y = 0;
|
|
scissor.extent.width = swapImage->imageInfo().extent.width;
|
|
scissor.extent.height = swapImage->imageInfo().extent.height;
|
|
|
|
m_context->setViewports(1, &viewport, &scissor);
|
|
|
|
m_context->bindResourceSampler(BindingIds::Sampler,
|
|
fitSize ? m_samplerFitting : m_samplerScaling);
|
|
|
|
m_blendMode.enableBlending = VK_FALSE;
|
|
m_context->setBlendMode(0, m_blendMode);
|
|
|
|
m_context->bindResourceView(BindingIds::Texture, m_backBufferView, nullptr);
|
|
m_context->draw(4, 1, 0, 0);
|
|
|
|
m_context->bindResourceSampler(BindingIds::GammaSmp, m_gammaSampler);
|
|
m_context->bindResourceView (BindingIds::GammaTex, m_gammaTextureView, nullptr);
|
|
|
|
if (m_hud != nullptr) {
|
|
m_blendMode.enableBlending = VK_TRUE;
|
|
m_context->setBlendMode(0, m_blendMode);
|
|
|
|
m_context->bindResourceView(BindingIds::Texture, m_hud->texture(), nullptr);
|
|
m_context->draw(4, 1, 0, 0);
|
|
}
|
|
|
|
m_device->submitCommandList(
|
|
m_context->endRecording(),
|
|
swapSemas.acquireSync,
|
|
swapSemas.presentSync);
|
|
|
|
m_swapchain->present(
|
|
swapSemas.presentSync);
|
|
}
|
|
}
|
|
|
|
|
|
void DxgiVkPresenter::UpdateBackBuffer(const Rc<DxvkImage>& Image) {
|
|
// Explicitly destroy the old stuff
|
|
m_backBuffer = Image;
|
|
m_backBufferResolve = nullptr;
|
|
m_backBufferView = nullptr;
|
|
|
|
// If a multisampled back buffer was requested, we also need to
|
|
// create a resolve image with otherwise identical properties.
|
|
// Multisample images cannot be sampled from.
|
|
if (Image->info().sampleCount != VK_SAMPLE_COUNT_1_BIT) {
|
|
DxvkImageCreateInfo resolveInfo;
|
|
resolveInfo.type = VK_IMAGE_TYPE_2D;
|
|
resolveInfo.format = Image->info().format;
|
|
resolveInfo.flags = 0;
|
|
resolveInfo.sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
|
resolveInfo.extent = Image->info().extent;
|
|
resolveInfo.numLayers = 1;
|
|
resolveInfo.mipLevels = 1;
|
|
resolveInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT
|
|
| VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
|
resolveInfo.stages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
|
|
| VK_PIPELINE_STAGE_TRANSFER_BIT;
|
|
resolveInfo.access = VK_ACCESS_SHADER_READ_BIT
|
|
| VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
resolveInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
resolveInfo.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
|
|
m_backBufferResolve = m_device->createImage(
|
|
resolveInfo, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
|
}
|
|
|
|
// Create an image view that allows the
|
|
// image to be bound as a shader resource.
|
|
DxvkImageViewCreateInfo viewInfo;
|
|
viewInfo.type = VK_IMAGE_VIEW_TYPE_2D;
|
|
viewInfo.format = Image->info().format;
|
|
viewInfo.aspect = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
viewInfo.minLevel = 0;
|
|
viewInfo.numLevels = 1;
|
|
viewInfo.minLayer = 0;
|
|
viewInfo.numLayers = 1;
|
|
|
|
m_backBufferView = m_device->createImageView(
|
|
m_backBufferResolve != nullptr
|
|
? m_backBufferResolve
|
|
: m_backBuffer,
|
|
viewInfo);
|
|
|
|
InitBackBuffer(m_backBuffer);
|
|
}
|
|
|
|
|
|
void DxgiVkPresenter::SetGammaControl(
|
|
const DXGI_VK_GAMMA_CURVE* pGammaCurve) {
|
|
m_context->beginRecording(
|
|
m_device->createCommandList());
|
|
|
|
m_context->updateImage(m_gammaTexture,
|
|
VkImageSubresourceLayers { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 },
|
|
VkOffset3D { 0, 0, 0 },
|
|
VkExtent3D { DXGI_VK_GAMMA_CP_COUNT, 1, 1 },
|
|
pGammaCurve, 0, 0);
|
|
|
|
m_device->submitCommandList(
|
|
m_context->endRecording(),
|
|
nullptr, nullptr);
|
|
}
|
|
|
|
|
|
void DxgiVkPresenter::RecreateSwapchain(DXGI_FORMAT Format, VkPresentModeKHR PresentMode, VkExtent2D WindowSize) {
|
|
if (m_surface == nullptr)
|
|
m_surface = CreateSurface();
|
|
|
|
DxvkSwapchainProperties options;
|
|
options.preferredSurfaceFormat = PickSurfaceFormat(Format);
|
|
options.preferredPresentMode = PickPresentMode(PresentMode);
|
|
options.preferredBufferSize = WindowSize;
|
|
|
|
const bool doRecreate =
|
|
options.preferredSurfaceFormat.format != m_options.preferredSurfaceFormat.format
|
|
|| options.preferredSurfaceFormat.colorSpace != m_options.preferredSurfaceFormat.colorSpace
|
|
|| options.preferredPresentMode != m_options.preferredPresentMode
|
|
|| options.preferredBufferSize.width != m_options.preferredBufferSize.width
|
|
|| options.preferredBufferSize.height != m_options.preferredBufferSize.height;
|
|
|
|
if (doRecreate) {
|
|
Logger::info(str::format(
|
|
"DxgiVkPresenter: Recreating swap chain: ",
|
|
"\n Format: ", options.preferredSurfaceFormat.format,
|
|
"\n Present mode: ", options.preferredPresentMode,
|
|
"\n Buffer size: ", options.preferredBufferSize.width, "x", options.preferredBufferSize.height));
|
|
|
|
if (m_swapchain == nullptr)
|
|
m_swapchain = m_device->createSwapchain(m_surface, options);
|
|
else
|
|
m_swapchain->changeProperties(options);
|
|
|
|
m_options = options;
|
|
}
|
|
}
|
|
|
|
|
|
VkSurfaceFormatKHR DxgiVkPresenter::PickSurfaceFormat(DXGI_FORMAT Fmt) const {
|
|
std::vector<VkSurfaceFormatKHR> formats;
|
|
|
|
switch (Fmt) {
|
|
case DXGI_FORMAT_R8G8B8A8_UNORM:
|
|
case DXGI_FORMAT_B8G8R8A8_UNORM: {
|
|
formats.push_back({ VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR });
|
|
formats.push_back({ VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR });
|
|
} break;
|
|
|
|
case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
|
|
case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: {
|
|
formats.push_back({ VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR });
|
|
formats.push_back({ VK_FORMAT_B8G8R8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR });
|
|
} break;
|
|
|
|
case DXGI_FORMAT_R10G10B10A2_UNORM: {
|
|
formats.push_back({ VK_FORMAT_A2B10G10R10_UNORM_PACK32, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR });
|
|
formats.push_back({ VK_FORMAT_A2R10G10B10_UNORM_PACK32, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR });
|
|
} break;
|
|
|
|
case DXGI_FORMAT_R16G16B16A16_FLOAT: {
|
|
formats.push_back({ VK_FORMAT_R16G16B16A16_SFLOAT, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR });
|
|
} break;
|
|
|
|
default:
|
|
Logger::warn(str::format("DxgiVkPresenter: Unknown format: ", Fmt));
|
|
}
|
|
|
|
return m_surface->pickSurfaceFormat(
|
|
formats.size(), formats.data());
|
|
}
|
|
|
|
|
|
VkPresentModeKHR DxgiVkPresenter::PickPresentMode(VkPresentModeKHR Preferred) const {
|
|
return m_surface->pickPresentMode(1, &Preferred);
|
|
}
|
|
|
|
|
|
Rc<DxvkSurface> DxgiVkPresenter::CreateSurface() {
|
|
HINSTANCE instance = reinterpret_cast<HINSTANCE>(
|
|
GetWindowLongPtr(m_window, GWLP_HINSTANCE));
|
|
|
|
return m_device->adapter()->createSurface(instance, m_window);
|
|
}
|
|
|
|
|
|
Rc<DxvkSampler> DxgiVkPresenter::CreateSampler(
|
|
VkFilter Filter,
|
|
VkSamplerAddressMode AddressMode) {
|
|
DxvkSamplerCreateInfo samplerInfo;
|
|
samplerInfo.magFilter = Filter;
|
|
samplerInfo.minFilter = Filter;
|
|
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
|
samplerInfo.mipmapLodBias = 0.0f;
|
|
samplerInfo.mipmapLodMin = 0.0f;
|
|
samplerInfo.mipmapLodMax = 0.0f;
|
|
samplerInfo.useAnisotropy = VK_FALSE;
|
|
samplerInfo.maxAnisotropy = 1.0f;
|
|
samplerInfo.addressModeU = AddressMode;
|
|
samplerInfo.addressModeV = AddressMode;
|
|
samplerInfo.addressModeW = AddressMode;
|
|
samplerInfo.compareToDepth = VK_FALSE;
|
|
samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
|
|
samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
|
|
samplerInfo.usePixelCoord = VK_FALSE;
|
|
return m_device->createSampler(samplerInfo);
|
|
}
|
|
|
|
|
|
Rc<DxvkImage> DxgiVkPresenter::CreateGammaTexture() {
|
|
DxvkImageCreateInfo info;
|
|
info.type = VK_IMAGE_TYPE_1D;
|
|
info.format = VK_FORMAT_R16G16B16A16_UNORM;
|
|
info.flags = 0;
|
|
info.sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
|
info.extent = { DXGI_VK_GAMMA_CP_COUNT, 1, 1 };
|
|
info.numLayers = 1;
|
|
info.mipLevels = 1;
|
|
info.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT
|
|
| VK_IMAGE_USAGE_SAMPLED_BIT;
|
|
info.stages = VK_PIPELINE_STAGE_TRANSFER_BIT
|
|
| VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
|
info.access = VK_ACCESS_TRANSFER_WRITE_BIT
|
|
| VK_ACCESS_SHADER_READ_BIT;
|
|
info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
info.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
return m_device->createImage(info, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
|
}
|
|
|
|
|
|
Rc<DxvkImageView> DxgiVkPresenter::CreateGammaTextureView() {
|
|
DxvkImageViewCreateInfo info;
|
|
info.type = VK_IMAGE_VIEW_TYPE_1D;
|
|
info.format = VK_FORMAT_R16G16B16A16_UNORM;
|
|
info.aspect = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
info.minLevel = 0;
|
|
info.numLevels = 1;
|
|
info.minLayer = 0;
|
|
info.numLayers = 1;
|
|
return m_device->createImageView(m_gammaTexture, info);
|
|
}
|
|
|
|
|
|
Rc<DxvkShader> DxgiVkPresenter::CreateVertexShader() {
|
|
const SpirvCodeBuffer codeBuffer(dxgi_presenter_vert);
|
|
|
|
return m_device->createShader(
|
|
VK_SHADER_STAGE_VERTEX_BIT,
|
|
0, nullptr, { 0u, 1u },
|
|
codeBuffer);
|
|
}
|
|
|
|
|
|
Rc<DxvkShader> DxgiVkPresenter::CreateFragmentShader() {
|
|
const SpirvCodeBuffer codeBuffer(dxgi_presenter_frag);
|
|
|
|
// Shader resource slots
|
|
const std::array<DxvkResourceSlot, 4> resourceSlots = {{
|
|
{ BindingIds::Sampler, VK_DESCRIPTOR_TYPE_SAMPLER, VK_IMAGE_VIEW_TYPE_MAX_ENUM },
|
|
{ BindingIds::Texture, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_IMAGE_VIEW_TYPE_2D },
|
|
{ BindingIds::GammaSmp, VK_DESCRIPTOR_TYPE_SAMPLER, VK_IMAGE_VIEW_TYPE_MAX_ENUM },
|
|
{ BindingIds::GammaTex, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_IMAGE_VIEW_TYPE_1D },
|
|
}};
|
|
|
|
// Create the actual shader module
|
|
return m_device->createShader(
|
|
VK_SHADER_STAGE_FRAGMENT_BIT,
|
|
resourceSlots.size(),
|
|
resourceSlots.data(),
|
|
{ 1u, 1u },
|
|
codeBuffer);
|
|
}
|
|
|
|
}
|