mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-04-02 11:01:50 -04:00
Vulkan: Implement post-processing. The Vulkan backend is now "feature-complete", and GL is now legacy :P
Bit hacky but works.
This commit is contained in:
parent
406ef92a74
commit
93e148fed6
10 changed files with 199 additions and 41 deletions
|
@ -69,7 +69,7 @@ class VulkanFBO;
|
|||
struct PostShaderUniforms {
|
||||
float texelDelta[2]; float pixelDelta[2];
|
||||
float time[4];
|
||||
bool video;
|
||||
float video;
|
||||
};
|
||||
|
||||
struct VirtualFramebuffer {
|
||||
|
|
|
@ -83,7 +83,22 @@ cbuffer data : register(b0) {
|
|||
float2 u_texelDelta;
|
||||
float2 u_pixelDelta;
|
||||
float4 u_time;
|
||||
bool u_video;
|
||||
float u_video;
|
||||
};
|
||||
)";
|
||||
|
||||
static const char *vulkanPrologue =
|
||||
R"(#version 430
|
||||
#extension GL_ARB_separate_shader_objects : enable
|
||||
#extension GL_ARB_shading_language_420pack : enable
|
||||
)";
|
||||
|
||||
static const char *pushconstantBufferDecl = R"(
|
||||
layout(push_constant) uniform data {
|
||||
vec2 u_texelDelta;
|
||||
vec2 u_pixelDelta;
|
||||
vec4 u_time;
|
||||
float u_video;
|
||||
};
|
||||
)";
|
||||
|
||||
|
@ -112,10 +127,74 @@ std::string Postprocess(std::string code, ShaderLanguage lang, Draw::ShaderStage
|
|||
return output;
|
||||
}
|
||||
|
||||
bool ConvertToVulkanGLSL(std::string *dest, TranslatedShaderMetadata *destMetadata, std::string src, Draw::ShaderStage stage, std::string *errorMessage) {
|
||||
std::stringstream out;
|
||||
|
||||
static struct {
|
||||
Draw::ShaderStage stage;
|
||||
const char *needle;
|
||||
const char *replacement;
|
||||
} replacements[] = {
|
||||
{ Draw::ShaderStage::VERTEX, "attribute vec4 a_position;", "layout(location = 0) in vec4 a_position;" },
|
||||
{ Draw::ShaderStage::VERTEX, "attribute vec2 a_texcoord0;", "layout(location = 1) in vec2 a_texcoord0;"},
|
||||
{ Draw::ShaderStage::VERTEX, "varying vec2 v_position;", "layout(location = 0) out vec2 v_position;" },
|
||||
{ Draw::ShaderStage::FRAGMENT, "varying vec2 v_position;", "layout(location = 0) in vec2 v_position;" },
|
||||
{ Draw::ShaderStage::FRAGMENT, "texture2D(", "texture(" },
|
||||
{ Draw::ShaderStage::FRAGMENT, "gl_FragColor", "fragColor0" },
|
||||
};
|
||||
|
||||
out << vulkanPrologue;
|
||||
if (stage == Draw::ShaderStage::FRAGMENT) {
|
||||
out << "layout (location = 0) out vec4 fragColor0;\n";
|
||||
}
|
||||
// Output the uniform buffer.
|
||||
out << pushconstantBufferDecl;
|
||||
|
||||
// Alright, now let's go through it line by line and zap the single uniforms
|
||||
// and perform replacements.
|
||||
std::string line;
|
||||
std::stringstream instream(src);
|
||||
while (std::getline(instream, line)) {
|
||||
char buffer[256];
|
||||
int vecSize, num;
|
||||
if (line.find("uniform bool") != std::string::npos) {
|
||||
continue;
|
||||
} else if (line.find("uniform sampler2D") == 0) {
|
||||
line = "layout(set = 0, binding = 0) " + line;
|
||||
} else if (line.find("uniform ") != std::string::npos) {
|
||||
continue;
|
||||
} else if (2 == sscanf(line.c_str(), "varying vec%d v_texcoord%d;", &vecSize, &num)) {
|
||||
if (stage == Draw::ShaderStage::FRAGMENT) {
|
||||
line = StringFromFormat("layout(location = %d) in vec%d v_texcoord%d;", num, vecSize, num);
|
||||
} else {
|
||||
line = StringFromFormat("layout(location = %d) out vec%d v_texcoord%d;", num, vecSize, num);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < ARRAY_SIZE(replacements); i++) {
|
||||
if (replacements[i].stage == stage)
|
||||
line = ReplaceAll(line, replacements[i].needle, replacements[i].replacement);
|
||||
}
|
||||
out << line << "\n";
|
||||
}
|
||||
|
||||
// DUMPLOG(src.c_str());
|
||||
// ILOG("---->");
|
||||
// DUMPLOG(LineNumberString(out.str()).c_str());
|
||||
|
||||
*dest = out.str();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TranslateShader(std::string *dest, ShaderLanguage destLang, TranslatedShaderMetadata *destMetadata, std::string src, ShaderLanguage srcLang, Draw::ShaderStage stage, std::string *errorMessage) {
|
||||
if (srcLang != GLSL_300 && srcLang != GLSL_140)
|
||||
return false;
|
||||
|
||||
if (srcLang == GLSL_140 || srcLang == GLSL_300 && destLang == GLSL_VULKAN) {
|
||||
// Let's just mess about at the string level, no need to recompile.
|
||||
bool result = ConvertToVulkanGLSL(dest, destMetadata, src, stage, errorMessage);
|
||||
return result;
|
||||
}
|
||||
|
||||
#if PPSSPP_PLATFORM(UWP)
|
||||
return false;
|
||||
#endif
|
||||
|
@ -126,8 +205,8 @@ bool TranslateShader(std::string *dest, ShaderLanguage destLang, TranslatedShade
|
|||
TBuiltInResource Resources;
|
||||
init_resources(Resources);
|
||||
|
||||
// Enable SPIR-V and Vulkan rules when parsing GLSL
|
||||
EShMessages messages = EShMessages::EShMsgDefault; // (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules);
|
||||
// Don't enable SPIR-V and Vulkan rules when parsing GLSL. Our postshaders are written in oldschool GLES 2.0.
|
||||
EShMessages messages = EShMessages::EShMsgDefault;
|
||||
|
||||
EShLanguage shaderStage = GetLanguage(stage);
|
||||
glslang::TShader shader(shaderStage);
|
||||
|
@ -166,8 +245,6 @@ bool TranslateShader(std::string *dest, ShaderLanguage destLang, TranslatedShade
|
|||
// Alright, step 1 done. Now let's take this SPIR-V shader and output in our desired format.
|
||||
|
||||
switch (destLang) {
|
||||
case GLSL_VULKAN:
|
||||
return false; // TODO
|
||||
#ifdef _WIN32
|
||||
case HLSL_DX9:
|
||||
{
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
#include "base/timeutil.h"
|
||||
#include "math/lin/matrix4x4.h"
|
||||
#include "math/dataconv.h"
|
||||
#include "i18n/i18n.h"
|
||||
#include "ext/native/file/vfs.h"
|
||||
#include "ext/native/thin3d/thin3d.h"
|
||||
|
||||
#include "Common/Vulkan/VulkanContext.h"
|
||||
|
@ -39,6 +41,7 @@
|
|||
#include "GPU/ge_constants.h"
|
||||
#include "GPU/GPUState.h"
|
||||
|
||||
#include "GPU/Common/ShaderTranslation.h"
|
||||
#include "GPU/Common/PostShader.h"
|
||||
#include "GPU/Common/TextureDecoder.h"
|
||||
#include "GPU/Common/FramebufferCommon.h"
|
||||
|
@ -155,6 +158,12 @@ void FramebufferManagerVulkan::DestroyDeviceObjects() {
|
|||
vulkan_->Delete().QueueDeleteSampler(linearSampler_);
|
||||
if (nearestSampler_ != VK_NULL_HANDLE)
|
||||
vulkan_->Delete().QueueDeleteSampler(nearestSampler_);
|
||||
|
||||
if (postVs_)
|
||||
vulkan_->Delete().QueueDeleteShaderModule(postVs_);
|
||||
if (postFs_)
|
||||
vulkan_->Delete().QueueDeleteShaderModule(postFs_);
|
||||
pipelinePostShader_ = VK_NULL_HANDLE; // actual pipeline should get destroyed by vulkan2d.
|
||||
}
|
||||
|
||||
void FramebufferManagerVulkan::NotifyClear(bool clearColor, bool clearAlpha, bool clearDepth, uint32_t color, float depth) {
|
||||
|
@ -180,28 +189,6 @@ void FramebufferManagerVulkan::NotifyClear(bool clearColor, bool clearAlpha, boo
|
|||
}
|
||||
}
|
||||
|
||||
void FramebufferManagerVulkan::UpdatePostShaderUniforms(int bufferWidth, int bufferHeight, int renderWidth, int renderHeight) {
|
||||
float u_delta = 1.0f / renderWidth;
|
||||
float v_delta = 1.0f / renderHeight;
|
||||
float u_pixel_delta = u_delta;
|
||||
float v_pixel_delta = v_delta;
|
||||
if (postShaderAtOutputResolution_) {
|
||||
float x, y, w, h;
|
||||
CenterDisplayOutputRect(&x, &y, &w, &h, 480.0f, 272.0f, (float)pixelWidth_, (float)pixelHeight_, ROTATION_LOCKED_HORIZONTAL);
|
||||
u_pixel_delta = (1.0f / w) * (480.0f / bufferWidth);
|
||||
v_pixel_delta = (1.0f / h) * (272.0f / bufferHeight);
|
||||
}
|
||||
|
||||
postUniforms_.texelDelta[0] = u_delta;
|
||||
postUniforms_.texelDelta[1] = v_delta;
|
||||
postUniforms_.pixelDelta[0] = u_pixel_delta;
|
||||
postUniforms_.pixelDelta[1] = v_pixel_delta;
|
||||
int flipCount = __DisplayGetFlipCount();
|
||||
int vCount = __DisplayGetVCount();
|
||||
float time[4] = { time_now(), (vCount % 60) * 1.0f / 60.0f, (float)vCount, (float)(flipCount % 60) };
|
||||
memcpy(postUniforms_.time, time, 4 * sizeof(float));
|
||||
}
|
||||
|
||||
void FramebufferManagerVulkan::Init() {
|
||||
FramebufferManagerCommon::Init();
|
||||
// Workaround for upscaling shaders where we force x1 resolution without saving it
|
||||
|
@ -349,6 +336,9 @@ void FramebufferManagerVulkan::DrawActiveTexture(float x, float y, float w, floa
|
|||
VkBuffer vbuffer;
|
||||
VkDeviceSize offset = push_->Push(vtx, sizeof(vtx), &vbuffer);
|
||||
renderManager->BindPipeline(cur2DPipeline_);
|
||||
if (cur2DPipeline_ == pipelinePostShader_) {
|
||||
renderManager->PushConstants(vulkan2D_->GetPipelineLayout(), VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_VERTEX_BIT, 0, (int)sizeof(postShaderUniforms_), &postShaderUniforms_);
|
||||
}
|
||||
renderManager->Draw(vulkan2D_->GetPipelineLayout(), descSet, 0, nullptr, vbuffer, offset, 4);
|
||||
}
|
||||
|
||||
|
@ -358,7 +348,23 @@ void FramebufferManagerVulkan::Bind2DShader() {
|
|||
}
|
||||
|
||||
void FramebufferManagerVulkan::BindPostShader(const PostShaderUniforms &uniforms) {
|
||||
Bind2DShader();
|
||||
if (!pipelinePostShader_) {
|
||||
if (usePostShader_) {
|
||||
CompilePostShader();
|
||||
}
|
||||
if (!usePostShader_) {
|
||||
SetNumExtraFBOs(0);
|
||||
Bind2DShader();
|
||||
return;
|
||||
} else {
|
||||
SetNumExtraFBOs(1);
|
||||
}
|
||||
}
|
||||
|
||||
postShaderUniforms_ = uniforms;
|
||||
// VulkanRenderManager *renderManager = (VulkanRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
|
||||
cur2DPipeline_ = pipelinePostShader_;
|
||||
|
||||
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE);
|
||||
}
|
||||
|
||||
|
@ -626,4 +632,74 @@ void FramebufferManagerVulkan::Resized() {
|
|||
if (UpdateSize()) {
|
||||
DestroyAllFBOs();
|
||||
}
|
||||
|
||||
// Might have a new post shader - let's compile it.
|
||||
CompilePostShader();
|
||||
}
|
||||
|
||||
void FramebufferManagerVulkan::CompilePostShader() {
|
||||
if (postVs_) {
|
||||
vulkan_->Delete().QueueDeleteShaderModule(postVs_);
|
||||
postVs_ = nullptr;
|
||||
}
|
||||
if (postFs_) {
|
||||
vulkan_->Delete().QueueDeleteShaderModule(postFs_);
|
||||
postFs_ = nullptr;
|
||||
}
|
||||
|
||||
const ShaderInfo *shaderInfo = nullptr;
|
||||
if (g_Config.sPostShaderName == "Off") {
|
||||
usePostShader_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
usePostShader_ = false;
|
||||
|
||||
ReloadAllPostShaderInfo();
|
||||
shaderInfo = GetPostShaderInfo(g_Config.sPostShaderName);
|
||||
std::string errorVSX, errorFSX;
|
||||
std::string vsSource;
|
||||
std::string fsSource;
|
||||
if (shaderInfo) {
|
||||
postShaderAtOutputResolution_ = shaderInfo->outputResolution;
|
||||
size_t sz;
|
||||
char *vs = (char *)VFSReadFile(shaderInfo->vertexShaderFile.c_str(), &sz);
|
||||
if (!vs)
|
||||
return;
|
||||
char *fs = (char *)VFSReadFile(shaderInfo->fragmentShaderFile.c_str(), &sz);
|
||||
if (!fs) {
|
||||
free(vs);
|
||||
return;
|
||||
}
|
||||
std::string vsSourceGLSL = vs;
|
||||
std::string fsSourceGLSL = fs;
|
||||
free(vs);
|
||||
free(fs);
|
||||
TranslatedShaderMetadata metaVS, metaFS;
|
||||
if (!TranslateShader(&vsSource, GLSL_VULKAN, &metaVS, vsSourceGLSL, GLSL_140, Draw::ShaderStage::VERTEX, &errorVSX))
|
||||
return;
|
||||
if (!TranslateShader(&fsSource, GLSL_VULKAN, &metaFS, fsSourceGLSL, GLSL_140, Draw::ShaderStage::FRAGMENT, &errorFSX))
|
||||
return;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
I18NCategory *gr = GetI18NCategory("Graphics");
|
||||
|
||||
// TODO: Delete the old pipeline?
|
||||
|
||||
std::string errorVS;
|
||||
std::string errorFS;
|
||||
postVs_ = CompileShaderModule(vulkan_, VK_SHADER_STAGE_VERTEX_BIT, vsSource.c_str(), &errorVS);
|
||||
postFs_ = CompileShaderModule(vulkan_, VK_SHADER_STAGE_FRAGMENT_BIT, fsSource.c_str(), &errorFS);
|
||||
|
||||
VkRenderPass backbufferRP = (VkRenderPass)draw_->GetNativeObject(Draw::NativeObject::BACKBUFFER_RENDERPASS);
|
||||
|
||||
if (postVs_ && postFs_) {
|
||||
pipelinePostShader_ = vulkan2D_->GetPipeline(backbufferRP, postVs_, postFs_, true, Vulkan2D::VK2DDepthStencilMode::NONE);
|
||||
} else {
|
||||
ELOG("Failed to compile.");
|
||||
}
|
||||
|
||||
|
||||
usePostShader_ = true;
|
||||
}
|
|
@ -86,6 +86,7 @@ public:
|
|||
void NotifyClear(bool clearColor, bool clearAlpha, bool clearDepth, uint32_t color, float depth);
|
||||
|
||||
protected:
|
||||
void CompilePostShader();
|
||||
void Bind2DShader() override;
|
||||
void BindPostShader(const PostShaderUniforms &uniforms) override;
|
||||
void SetViewport2D(int x, int y, int w, int h) override;
|
||||
|
@ -100,8 +101,6 @@ private:
|
|||
// The returned texture does not need to be free'd, might be returned from a pool (currently single entry)
|
||||
void MakePixelTexture(const u8 *srcPixels, GEBufferFormat srcPixelFormat, int srcStride, int width, int height, float &u1, float &v1) override;
|
||||
|
||||
void UpdatePostShaderUniforms(int bufferWidth, int bufferHeight, int renderWidth, int renderHeight);
|
||||
|
||||
void InitDeviceObjects();
|
||||
void DestroyDeviceObjects();
|
||||
|
||||
|
@ -130,16 +129,20 @@ private:
|
|||
VkPipelineCache pipelineCache2D_;
|
||||
|
||||
// Basic shaders
|
||||
VkShaderModule fsBasicTex_;
|
||||
VkShaderModule vsBasicTex_;
|
||||
VkShaderModule fsBasicTex_ = VK_NULL_HANDLE;
|
||||
VkShaderModule vsBasicTex_ = VK_NULL_HANDLE;
|
||||
|
||||
VkShaderModule stencilVs_ = VK_NULL_HANDLE;
|
||||
VkShaderModule stencilFs_ = VK_NULL_HANDLE;
|
||||
|
||||
|
||||
VkPipeline cur2DPipeline_ = VK_NULL_HANDLE;
|
||||
|
||||
// Postprocessing
|
||||
VkPipeline pipelinePostShader_;
|
||||
VkShaderModule postVs_ = VK_NULL_HANDLE;
|
||||
VkShaderModule postFs_ = VK_NULL_HANDLE;
|
||||
VkPipeline pipelinePostShader_ = VK_NULL_HANDLE;
|
||||
PostShaderUniforms postShaderUniforms_;
|
||||
|
||||
VkSampler linearSampler_;
|
||||
VkSampler nearestSampler_;
|
||||
|
|
|
@ -95,11 +95,11 @@ GPU_Vulkan::GPU_Vulkan(GraphicsContext *gfxCtx, Draw::DrawContext *draw)
|
|||
drawEngine_.SetFramebufferManager(framebufferManagerVulkan_);
|
||||
drawEngine_.SetShaderManager(shaderManagerVulkan_);
|
||||
drawEngine_.SetPipelineManager(pipelineManager_);
|
||||
framebufferManagerVulkan_->SetVulkan2D(&vulkan2D_);
|
||||
framebufferManagerVulkan_->Init();
|
||||
framebufferManagerVulkan_->SetTextureCache(textureCacheVulkan_);
|
||||
framebufferManagerVulkan_->SetDrawEngine(&drawEngine_);
|
||||
framebufferManagerVulkan_->SetShaderManager(shaderManagerVulkan_);
|
||||
framebufferManagerVulkan_->SetVulkan2D(&vulkan2D_);
|
||||
textureCacheVulkan_->SetDepalShaderCache(&depalShaderCache_);
|
||||
textureCacheVulkan_->SetFramebufferManager(framebufferManagerVulkan_);
|
||||
textureCacheVulkan_->SetShaderManager(shaderManagerVulkan_);
|
||||
|
|
|
@ -63,12 +63,13 @@ void Vulkan2D::DestroyDeviceObjects() {
|
|||
|
||||
void Vulkan2D::InitDeviceObjects() {
|
||||
pipelineCache_ = vulkan_->CreatePipelineCache();
|
||||
// All resources we need for PSP drawing. Usually only bindings 0 and 2-4 are populated.
|
||||
VkDescriptorSetLayoutBinding bindings[2] = {};
|
||||
// Texture.
|
||||
bindings[0].descriptorCount = 1;
|
||||
bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
bindings[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
bindings[0].binding = 0;
|
||||
// In depal, this second texture is used for the palette.
|
||||
bindings[1].descriptorCount = 1;
|
||||
bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
bindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
|
@ -98,8 +99,8 @@ void Vulkan2D::InitDeviceObjects() {
|
|||
|
||||
VkPushConstantRange push = {};
|
||||
push.offset = 0;
|
||||
push.size = 16;
|
||||
push.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
push.size = 48;
|
||||
push.stageFlags = VK_SHADER_STAGE_VERTEX_BIT|VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
|
||||
VkPipelineLayoutCreateInfo pl = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO };
|
||||
pl.pPushConstantRanges = &push;
|
||||
|
|
|
@ -104,7 +104,7 @@ private:
|
|||
VK2DDepthStencilMode depthStencilMode;
|
||||
bool readVertices;
|
||||
bool operator < (const PipelineKey &other) const {
|
||||
return std::tie(vs, fs, rp, depthStencilMode, readVertices) < std::tie(other.vs, other.fs, other.rp, depthStencilMode, readVertices);
|
||||
return std::tie(vs, fs, rp, depthStencilMode, readVertices) < std::tie(other.vs, other.fs, other.rp, other.depthStencilMode, other.readVertices);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -88,6 +88,7 @@ const char *GetFn(const char *fn);
|
|||
OutputDebugStringUTF8(temp); \
|
||||
} \
|
||||
} while (false)
|
||||
#define DUMPLOG(x) OutputDebugStringUTF8(x);
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define DLOG(...) XLOG_IMPL("D", __VA_ARGS__)
|
||||
|
|
|
@ -74,7 +74,7 @@ struct VkRenderData {
|
|||
VkShaderStageFlags stages;
|
||||
uint8_t offset;
|
||||
uint8_t size;
|
||||
uint8_t data[32]; // Should be enough for now.
|
||||
uint8_t data[40]; // Should be enough for now.
|
||||
} push;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -139,7 +139,7 @@ public:
|
|||
|
||||
void PushConstants(VkPipelineLayout pipelineLayout, VkShaderStageFlags stages, int offset, int size, void *constants) {
|
||||
_dbg_assert_(G3D, curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
|
||||
assert(size + offset < 32);
|
||||
assert(size + offset < 40);
|
||||
VkRenderData data{ VKRRenderCommand::PUSH_CONSTANTS };
|
||||
data.push.pipelineLayout = pipelineLayout;
|
||||
data.push.stages = stages;
|
||||
|
|
Loading…
Add table
Reference in a new issue