Permalink
Browse files

Vulkan: Implement post-processing. The Vulkan backend is now "feature…

…-complete", and GL is now legacy :P

Bit hacky but works.
  • Loading branch information...
hrydgard committed Nov 1, 2017
1 parent 406ef92 commit 93e148fed6c3478388f2f648928c73b103b54947
@@ -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 @@ class FramebufferManagerVulkan : public FramebufferManagerCommon {
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 @@ class FramebufferManagerVulkan : public FramebufferManagerCommon {
// 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 @@ class FramebufferManagerVulkan : public FramebufferManagerCommon {
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;
View
@@ -104,7 +104,7 @@ class Vulkan2D {
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__)
Oops, something went wrong.

0 comments on commit 93e148f

Please sign in to comment.