Always use array textures for framebuffers in Vulkan for simplicity.

This commit is contained in:
Henrik Rydgård 2022-10-25 23:32:28 +02:00
parent 2bea495981
commit 91259aaad7
12 changed files with 83 additions and 40 deletions

View File

@ -101,7 +101,7 @@ public:
void InvalidateCachedState() override;
void BindTextures(int start, int count, Texture **textures) override;
void BindTextures(int start, int count, Texture **textures, TextureBindFlags flags) override;
void BindNativeTexture(int index, void *nativeTexture) override;
void BindSamplerStates(int start, int count, SamplerState **states) override;
void BindVertexBuffers(int start, int count, Buffer **buffers, const int *offsets) override;
@ -1381,7 +1381,7 @@ Framebuffer *D3D11DrawContext::CreateFramebuffer(const FramebufferDesc &desc) {
return fb;
}
void D3D11DrawContext::BindTextures(int start, int count, Texture **textures) {
void D3D11DrawContext::BindTextures(int start, int count, Texture **textures, TextureBindFlags flags) {
// Collect the resource views from the textures.
ID3D11ShaderResourceView *views[MAX_BOUND_TEXTURES];
_assert_(start + count <= ARRAY_SIZE(views));

View File

@ -538,7 +538,7 @@ public:
void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) override;
void BindTextures(int start, int count, Texture **textures) override;
void BindTextures(int start, int count, Texture **textures, TextureBindFlags flags) override;
void BindNativeTexture(int index, void *nativeTexture) override;
void BindSamplerStates(int start, int count, SamplerState **states) override {
@ -912,7 +912,7 @@ Texture *D3D9Context::CreateTexture(const TextureDesc &desc) {
return tex;
}
void D3D9Context::BindTextures(int start, int count, Texture **textures) {
void D3D9Context::BindTextures(int start, int count, Texture **textures, TextureBindFlags flags) {
_assert_(start + count <= MAX_BOUND_TEXTURES);
for (int i = start; i < start + count; i++) {
D3D9Texture *tex = static_cast<D3D9Texture *>(textures[i - start]);

View File

@ -397,7 +397,7 @@ public:
curPipeline_->depthStencil->stencilPass);
}
void BindTextures(int start, int count, Texture **textures) override;
void BindTextures(int start, int count, Texture **textures, TextureBindFlags flags) override;
void BindNativeTexture(int sampler, void *nativeTexture) override;
void BindPipeline(Pipeline *pipeline) override;
@ -1127,7 +1127,7 @@ Pipeline *OpenGLContext::CreateGraphicsPipeline(const PipelineDesc &desc, const
}
}
void OpenGLContext::BindTextures(int start, int count, Texture **textures) {
void OpenGLContext::BindTextures(int start, int count, Texture **textures, TextureBindFlags flags) {
_assert_(start + count <= MAX_TEXTURE_SLOTS);
for (int i = start; i < start + count; i++) {
OpenGLTexture *glTex = static_cast<OpenGLTexture *>(textures[i - start]);

View File

@ -4,9 +4,12 @@
#include <cstdint>
#include <cstddef> // for size_t
#include "Common/Common.h"
// GLSL_1xx and GLSL_3xx each cover a lot of sub variants. All the little quirks
// that differ are covered in ShaderLanguageDesc.
// Defined as a bitmask so stuff like GetSupportedShaderLanguages can return combinations.
// TODO: We can probably move away from this distinction soon, now that we mostly generate/translate shaders.
enum ShaderLanguage {
GLSL_1xx = 1,
GLSL_3xx = 2,
@ -30,7 +33,6 @@ enum class ShaderStage {
const char *ShaderStageAsString(ShaderStage lang);
struct ShaderLanguageDesc {
ShaderLanguageDesc() {}
explicit ShaderLanguageDesc(ShaderLanguage lang);
@ -91,14 +93,18 @@ struct UniformDef {
int index;
};
enum class SamplerFlags {
ARRAY_ON_VULKAN = 1,
};
ENUM_CLASS_BITOPS(SamplerFlags);
struct SamplerDef {
int binding; // Might only be used by some backends.
const char *name;
bool array;
SamplerFlags flags;
// TODO: Might need unsigned samplers, 3d samplers, or other types in the future.
};
// For passing error messages from shader compilation (and other critical issues) back to the host.
// This can run on any thread - be aware!
// TODO: See if we can find a less generic name for this.

View File

@ -465,15 +465,6 @@ void ShaderWriter::DeclareSamplers(Slice<SamplerDef> samplers) {
samplerDefs_ = samplers;
}
const SamplerDef *ShaderWriter::GetSamplerDef(const char *name) const {
for (int i = 0; i < (int)samplers_.size(); i++) {
if (!strcmp(samplers_[i].name, name)) {
return &samplers_[i];
}
}
return nullptr;
}
void ShaderWriter::DeclareTexture2D(const SamplerDef &def) {
switch (lang_.shaderLanguage) {
case HLSL_D3D11:
@ -484,7 +475,7 @@ void ShaderWriter::DeclareTexture2D(const SamplerDef &def) {
break;
case GLSL_VULKAN:
// In the thin3d descriptor set layout, textures start at 1 in set 0. Hence the +1.
if ((flags_ & ShaderWriterFlags::FS_AUTO_STEREO) && def.array) {
if (def.flags & SamplerFlags::ARRAY_ON_VULKAN) {
F("layout(set = 0, binding = %d) uniform sampler2DArray %s;\n", def.binding + texBindingBase_, def.name);
} else {
F("layout(set = 0, binding = %d) uniform sampler2D %s;\n", def.binding + texBindingBase_, def.name);
@ -518,7 +509,7 @@ ShaderWriter &ShaderWriter::SampleTexture2D(const char *sampName, const char *uv
break;
default:
// Note: we ignore the sampler. make sure you bound samplers to the textures correctly.
if (samp && samp->array) {
if (samp && (samp->flags & SamplerFlags::ARRAY_ON_VULKAN)) {
const char *index = (flags_ & ShaderWriterFlags::FS_AUTO_STEREO) ? "float(gl_ViewIndex)" : "0.0";
F("%s(%s, vec3(%s, %s))", lang_.texture, sampName, uv, index);
} else {
@ -542,7 +533,7 @@ ShaderWriter &ShaderWriter::SampleTexture2DOffset(const char *sampName, const ch
break;
default:
// Note: we ignore the sampler. make sure you bound samplers to the textures correctly.
if (samp->array) {
if (samp && (samp->flags & SamplerFlags::ARRAY_ON_VULKAN)) {
const char *index = (flags_ & ShaderWriterFlags::FS_AUTO_STEREO) ? "float(gl_ViewIndex)" : "0.0";
F("%sOffset(%s, vec3(%s, %s), ivec3(%d, %d))", lang_.texture, sampName, uv, index, offX, offY);
} else {
@ -566,8 +557,9 @@ ShaderWriter &ShaderWriter::LoadTexture2D(const char *sampName, const char *uv,
break;
default:
// Note: we ignore the sampler. make sure you bound samplers to the textures correctly.
if ((flags_ & ShaderWriterFlags::FS_AUTO_STEREO) && samp->array) {
F("texelFetch(%s, %s, %d)", sampName, uv, level);
if (samp && (samp->flags & SamplerFlags::ARRAY_ON_VULKAN)) {
const char *index = (flags_ & ShaderWriterFlags::FS_AUTO_STEREO) ? "gl_ViewIndex" : "0";
F("texelFetch(%s, vec3(%s, %s), %d)", sampName, uv, index, level);
} else {
F("texelFetch(%s, %s, %d)", sampName, uv, level);
}

View File

@ -33,7 +33,7 @@ struct VaryingDef {
enum class ShaderWriterFlags {
NONE = 0,
FS_WRITE_DEPTH = 1,
FS_AUTO_STEREO = 2, // Automatically makes sampler 0 an array sampler, and samples it by gl_ViewIndex. Useful for stereo rendering.
FS_AUTO_STEREO = 2, // Automatically indexes makes samplers tagged with `array` by gl_ViewIndex. Useful for stereo rendering.
};
ENUM_CLASS_BITOPS(ShaderWriterFlags);
@ -119,8 +119,6 @@ private:
void Preamble(Slice<const char *> extensions);
const SamplerDef *GetSamplerDef(const char *name) const;
char *p_;
const ShaderLanguageDesc &lang_;
const ShaderStage stage_;

View File

@ -123,6 +123,13 @@ bool VulkanTexture::CreateDirect(VkCommandBuffer cmd, int w, int h, int depth, i
_assert_(res == VK_ERROR_OUT_OF_HOST_MEMORY || res == VK_ERROR_OUT_OF_DEVICE_MEMORY || res == VK_ERROR_TOO_MANY_OBJECTS);
return false;
}
// Additionally, create an array view, but only if it's a 2D texture.
if (view_info.viewType == VK_IMAGE_VIEW_TYPE_2D) {
view_info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
res = vkCreateImageView(vulkan_->GetDevice(), &view_info, NULL, &arrayView_);
_assert_(res == VK_SUCCESS);
}
return true;
}
@ -245,6 +252,9 @@ void VulkanTexture::Destroy() {
if (view_ != VK_NULL_HANDLE) {
vulkan_->Delete().QueueDeleteImageView(view_);
}
if (arrayView_ != VK_NULL_HANDLE) {
vulkan_->Delete().QueueDeleteImageView(arrayView_);
}
if (image_ != VK_NULL_HANDLE) {
_dbg_assert_(allocation_ != VK_NULL_HANDLE);
vulkan_->Delete().QueueDeleteImageAllocation(image_, allocation_);

View File

@ -51,6 +51,9 @@ public:
// Used for sampling, generally.
VkImageView GetImageView() const { return view_; }
// For use with some shaders, we might want to view it as a single entry array for convenience.
VkImageView GetImageArrayView() const { return arrayView_; }
int32_t GetWidth() const { return width_; }
int32_t GetHeight() const { return height_; }
int32_t GetNumMips() const { return numMips_; }
@ -62,6 +65,7 @@ private:
VulkanContext *vulkan_;
VkImage image_ = VK_NULL_HANDLE;
VkImageView view_ = VK_NULL_HANDLE;
VkImageView arrayView_ = VK_NULL_HANDLE;
VmaAllocation allocation_ = VK_NULL_HANDLE;
int16_t width_ = 0;

View File

@ -338,10 +338,16 @@ public:
if (vkTex_) {
vkTex_->Touch();
return vkTex_->GetImageView();
} else {
// This would be bad.
return VK_NULL_HANDLE;
}
return VK_NULL_HANDLE; // This would be bad.
}
VkImageView GetImageArrayView() {
if (vkTex_) {
vkTex_->Touch();
return vkTex_->GetImageArrayView();
}
return VK_NULL_HANDLE; // This would be bad.
}
private:
@ -417,7 +423,7 @@ public:
void SetStencilParams(uint8_t refValue, uint8_t writeMask, uint8_t compareMask) override;
void BindSamplerStates(int start, int count, SamplerState **state) override;
void BindTextures(int start, int count, Texture **textures) override;
void BindTextures(int start, int count, Texture **textures, TextureBindFlags flags) override;
void BindNativeTexture(int sampler, void *nativeTexture) override;
void BindPipeline(Pipeline *pipeline) override {
@ -520,6 +526,7 @@ private:
MAX_FRAME_COMMAND_BUFFERS = 256,
};
AutoRef<VKTexture> boundTextures_[MAX_BOUND_TEXTURES];
TextureBindFlags boundTextureFlags_[MAX_BOUND_TEXTURES];
AutoRef<VKSamplerState> boundSamplers_[MAX_BOUND_TEXTURES];
VkImageView boundImageView_[MAX_BOUND_TEXTURES]{};
@ -994,7 +1001,11 @@ VkDescriptorSet VKContext::GetOrCreateDescriptorSet(VkBuffer buf) {
FrameData *frame = &frame_[vulkan_->GetCurFrame()];
for (int i = 0; i < MAX_BOUND_TEXTURES; ++i) {
key.imageViews_[i] = boundTextures_[i] ? boundTextures_[i]->GetImageView() : boundImageView_[i];
if (boundTextures_[i]) {
key.imageViews_[i] = (boundTextureFlags_[i] & TextureBindFlags::VULKAN_BIND_ARRAY) ? boundTextures_[i]->GetImageArrayView() : boundTextures_[i]->GetImageView();
} else {
key.imageViews_[i] = boundImageView_[i];
}
key.samplers_[i] = boundSamplers_[i];
}
key.buffer_ = buf;
@ -1296,11 +1307,21 @@ void VKContext::UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset,
memcpy(buf->data_ + offset, data, size);
}
void VKContext::BindTextures(int start, int count, Texture **textures) {
void VKContext::BindTextures(int start, int count, Texture **textures, TextureBindFlags flags) {
_assert_(start + count <= MAX_BOUND_TEXTURES);
for (int i = start; i < start + count; i++) {
boundTextures_[i] = static_cast<VKTexture *>(textures[i - start]);
boundImageView_[i] = boundTextures_[i] ? boundTextures_[i]->GetImageView() : GetNullTexture()->GetImageView();
boundTextureFlags_[i] = flags;
if (boundTextures_[i]) {
// NOTE: These image views are actually not used, it seems - they get overridden in GetOrCreateDescriptorSet
if (flags & TextureBindFlags::VULKAN_BIND_ARRAY) {
boundImageView_[i] = boundTextures_[i]->GetImageArrayView();
} else {
boundImageView_[i] = boundTextures_[i]->GetImageView();
}
} else {
boundImageView_[i] = GetNullTexture()->GetImageView();
}
}
}

View File

@ -13,6 +13,7 @@
#include <string>
#include <vector>
#include "Common/Common.h"
#include "Common/GPU/DataFormat.h"
#include "Common/GPU/Shader.h"
#include "Common/Data/Collections/Slice.h"
@ -598,6 +599,12 @@ struct RenderPassInfo {
const int ALL_LAYERS = -1;
enum class TextureBindFlags {
NONE = 0,
VULKAN_BIND_ARRAY = 1,
};
ENUM_CLASS_BITOPS(TextureBindFlags);
class DrawContext {
public:
virtual ~DrawContext();
@ -688,7 +695,7 @@ public:
virtual void SetStencilParams(uint8_t refValue, uint8_t writeMask, uint8_t compareMask) = 0;
virtual void BindSamplerStates(int start, int count, SamplerState **state) = 0;
virtual void BindTextures(int start, int count, Texture **textures) = 0;
virtual void BindTextures(int start, int count, Texture **textures, TextureBindFlags flags = TextureBindFlags::NONE) = 0;
virtual void BindVertexBuffers(int start, int count, Buffer **buffers, const int *offsets) = 0;
virtual void BindIndexBuffer(Buffer *indexBuffer, int offset) = 0;

View File

@ -37,7 +37,7 @@ static const VaryingDef varyings[1] = {
};
static const SamplerDef samplers[1] = {
{ 0, "tex" },
{ 0, "tex", SamplerFlags::ARRAY_ON_VULKAN },
};
const UniformDef g_draw2Duniforms[2] = {
@ -88,8 +88,8 @@ Draw2DPipelineInfo GenerateDraw2DCopyColorRect2LinFs(ShaderWriter &writer) {
}
Draw2DPipelineInfo GenerateDraw2DCopyDepthFs(ShaderWriter &writer) {
writer.DeclareSamplers(samplers);
writer.SetFlags(ShaderWriterFlags::FS_WRITE_DEPTH);
writer.DeclareSamplers(samplers);
writer.BeginFSMain(Slice<UniformDef>::empty(), varyings);
writer.C(" vec4 outColor = vec4(0.0, 0.0, 0.0, 0.0);\n");
writer.C(" gl_FragDepth = ").SampleTexture2D("tex", "v_texcoord.xy").C(".x;\n");
@ -103,8 +103,8 @@ Draw2DPipelineInfo GenerateDraw2DCopyDepthFs(ShaderWriter &writer) {
}
Draw2DPipelineInfo GenerateDraw2D565ToDepthFs(ShaderWriter &writer) {
writer.DeclareSamplers(samplers);
writer.SetFlags(ShaderWriterFlags::FS_WRITE_DEPTH);
writer.DeclareSamplers(samplers);
writer.BeginFSMain(Slice<UniformDef>::empty(), varyings);
writer.C(" vec4 outColor = vec4(0.0, 0.0, 0.0, 0.0);\n");
// Unlike when just copying a depth buffer, here we're generating new depth values so we'll
@ -123,8 +123,8 @@ Draw2DPipelineInfo GenerateDraw2D565ToDepthFs(ShaderWriter &writer) {
}
Draw2DPipelineInfo GenerateDraw2D565ToDepthDeswizzleFs(ShaderWriter &writer) {
writer.DeclareSamplers(samplers);
writer.SetFlags(ShaderWriterFlags::FS_WRITE_DEPTH);
writer.DeclareSamplers(samplers);
writer.BeginFSMain(g_draw2Duniforms, varyings);
writer.C(" vec4 outColor = vec4(0.0, 0.0, 0.0, 0.0);\n");
// Unlike when just copying a depth buffer, here we're generating new depth values so we'll
@ -182,6 +182,10 @@ void Draw2D::Ensure2DResources() {
if (!draw2DVs_) {
char *vsCode = new char[8192];
ShaderWriterFlags flags = ShaderWriterFlags::NONE;
if (gstate_c.Use(GPU_USE_SINGLE_PASS_STEREO)) {
flags = ShaderWriterFlags::FS_AUTO_STEREO;
}
ShaderWriter writer(vsCode, shaderLanguageDesc, ShaderStage::Vertex);
GenerateDraw2DVS(writer);
_assert_msg_(strlen(vsCode) < 8192, "Draw2D VS length error: %d", (int)strlen(vsCode));
@ -315,6 +319,7 @@ void Draw2D::DrawStrip2D(Draw::Texture *tex, Draw2DVertex *verts, int vertexCoun
draw_->UpdateDynamicUniformBuffer(&ub, sizeof(ub));
if (tex) {
// This won't work since all the shaders above expect array textures on Vulkan.
draw_->BindTextures(TEX_SLOT_PSP_TEXTURE, 1, &tex);
}
draw_->BindSamplerStates(TEX_SLOT_PSP_TEXTURE, 1, linearFilter ? &draw2DSamplerLinear_ : &draw2DSamplerNearest_);

View File

@ -1140,7 +1140,7 @@ void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int
Draw::Texture *pixelsTex = MakePixelTexture(srcPixels, srcPixelFormat, srcStride, width, height);
if (pixelsTex) {
draw_->BindTextures(0, 1, &pixelsTex);
draw_->BindTextures(0, 1, &pixelsTex, Draw::TextureBindFlags::VULKAN_BIND_ARRAY);
// TODO: Replace with draw2D_.Blit() directly.
DrawActiveTexture(dstX, dstY, width, height, vfb->bufferWidth, vfb->bufferHeight, u0, v0, u1, v1, ROTATION_LOCKED_HORIZONTAL, flags);
@ -3042,7 +3042,7 @@ void FramebufferManagerCommon::BlitUsingRaster(
draw_->BindTexture(0, nullptr);
// This will get optimized away in case it's already bound (in VK and GL at least..)
draw_->BindFramebufferAsRenderTarget(dest, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }, tag ? tag : "BlitUsingRaster");
draw_->BindFramebufferAsTexture(src, 0, pipeline->info.readChannel == RASTER_COLOR ? Draw::FB_COLOR_BIT : Draw::FB_DEPTH_BIT, 0);
draw_->BindFramebufferAsTexture(src, Draw::ALL_LAYERS, pipeline->info.readChannel == RASTER_COLOR ? Draw::FB_COLOR_BIT : Draw::FB_DEPTH_BIT, 0);
if (destX1 == 0.0f && destY1 == 0.0f && destX2 >= destW && destY2 >= destH) {
// We overwrite the whole channel of the framebuffer, so we can invalidate the current contents.