CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/GPU/Common/PresentationCommon.cpp
Views: 1401
// Copyright (c) 2012- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include <cmath>18#include <set>19#include <cstdint>20#include <algorithm>2122#include "Common/GPU/thin3d.h"2324#include "Common/System/Display.h"25#include "Common/System/System.h"26#include "Common/System/OSD.h"27#include "Common/File/VFS/VFS.h"28#include "Common/VR/PPSSPPVR.h"29#include "Common/Log.h"30#include "Common/TimeUtil.h"31#include "Core/Config.h"32#include "Core/ConfigValues.h"33#include "Core/System.h"34#include "Core/HW/Display.h"35#include "GPU/Common/PostShader.h"36#include "GPU/Common/PresentationCommon.h"37#include "GPU/GPUState.h"38#include "Common/GPU/ShaderTranslation.h"3940struct Vertex {41float x, y, z;42float u, v;43uint32_t rgba;44};4546FRect GetScreenFrame(float pixelWidth, float pixelHeight) {47FRect rc = FRect{480.0f,490.0f,50pixelWidth,51pixelHeight,52};5354bool applyInset = !g_Config.bIgnoreScreenInsets;5556if (applyInset) {57// Remove the DPI scale to get back to pixels.58float left = System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_LEFT) / g_display.dpi_scale_x;59float right = System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_RIGHT) / g_display.dpi_scale_x;60float top = System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_TOP) / g_display.dpi_scale_y;61float bottom = System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_BOTTOM) / g_display.dpi_scale_y;6263// Adjust left edge to compensate for cutouts (notches) if any.64rc.x += left;65rc.w -= (left + right);66rc.y += top;67rc.h -= (top + bottom);68}69return rc;70}7172void CalculateDisplayOutputRect(FRect *rc, float origW, float origH, const FRect &frame, int rotation) {73float outW;74float outH;7576bool rotated = rotation == ROTATION_LOCKED_VERTICAL || rotation == ROTATION_LOCKED_VERTICAL180;7778bool stretch = g_Config.bDisplayStretch && !g_Config.bDisplayIntegerScale;7980float offsetX = g_Config.fDisplayOffsetX;81float offsetY = g_Config.fDisplayOffsetY;8283float scale = g_Config.fDisplayScale;84float aspectRatioAdjust = g_Config.fDisplayAspectRatio;8586float origRatio = !rotated ? origW / origH : origH / origW;87float frameRatio = frame.w / frame.h;8889if (stretch) {90// Automatically set aspect ratio to match the display, IF the rotation matches the output display ratio! Otherwise, just91// sets standard aspect ratio because actually stretching will just look silly.92bool globalRotated = g_display.rotation == DisplayRotation::ROTATE_90 || g_display.rotation == DisplayRotation::ROTATE_270;93if (rotated == g_display.dp_yres > g_display.dp_xres) {94origRatio = frameRatio;95} else {96origRatio *= aspectRatioAdjust;97}98} else {99origRatio *= aspectRatioAdjust;100}101102float scaledWidth = frame.w * scale;103float scaledHeight = frame.h * scale;104105if (origRatio > frameRatio) {106// Image is wider than frame. Center vertically.107outW = scaledWidth;108outH = scaledWidth / origRatio;109} else {110// Image is taller than frame. Center horizontally.111outW = scaledHeight * origRatio;112outH = scaledHeight;113}114115// Ye olde 1080p hack: If everything is setup to exactly cover the screen (defaults), and the screen display aspect ratio is 16:9,116// cut off one line from the top and bottom.117if (scale == 1.0f && aspectRatioAdjust == 1.0f && offsetX == 0.5f && offsetY == 0.5f && !g_Config.bDisplayIntegerScale && g_Config.bDisplayCropTo16x9) {118if (fabsf(frame.w / frame.h - 16.0f / 9.0f) < 0.0001f) {119outW *= 272.0f / 270.0f;120outH *= 272.0f / 270.0f;121}122}123124if (g_Config.bDisplayIntegerScale) {125float wDim = 480.0f;126if (rotated) {127wDim = 272.0f;128}129130int zoom = g_Config.iInternalResolution;131if (zoom == 0) {132// Auto (1:1) mode, not super meaningful with integer scaling, but let's do something that makes133// some sense. use the longest dimension, just to have something. round down.134if (!g_Config.IsPortrait()) {135zoom = (PSP_CoreParameter().pixelWidth) / 480;136} else {137zoom = (PSP_CoreParameter().pixelHeight) / 480;138}139}140// If integer scaling, limit ourselves to even multiples of the rendered resolution,141// to make sure all the pixels are square.142wDim *= zoom;143outW = std::max(1.0f, floorf(outW / wDim)) * wDim;144outH = outW / origRatio;145}146147if (IsVREnabled()) {148rc->x = 0;149rc->y = 0;150rc->w = floorf(frame.w);151rc->h = floorf(frame.h);152outW = frame.w;153outH = frame.h;154} else {155rc->x = floorf(frame.x + frame.w * offsetX - outW * 0.5f);156rc->y = floorf(frame.y + frame.h * offsetY - outH * 0.5f);157rc->w = floorf(outW);158rc->h = floorf(outH);159}160}161162PresentationCommon::PresentationCommon(Draw::DrawContext *draw) : draw_(draw) {163CreateDeviceObjects();164}165166PresentationCommon::~PresentationCommon() {167DestroyDeviceObjects();168}169170void PresentationCommon::GetCardboardSettings(CardboardSettings *cardboardSettings) const {171if (!g_Config.bEnableCardboardVR) {172cardboardSettings->enabled = false;173return;174}175176// Calculate Cardboard Settings177float cardboardScreenScale = g_Config.iCardboardScreenSize / 100.0f;178float cardboardScreenWidth = pixelWidth_ / 2.0f * cardboardScreenScale;179float cardboardScreenHeight = pixelHeight_ * cardboardScreenScale;180float cardboardMaxXShift = (pixelWidth_ / 2.0f - cardboardScreenWidth) / 2.0f;181float cardboardUserXShift = g_Config.iCardboardXShift / 100.0f * cardboardMaxXShift;182float cardboardLeftEyeX = cardboardMaxXShift + cardboardUserXShift;183float cardboardRightEyeX = pixelWidth_ / 2.0f + cardboardMaxXShift - cardboardUserXShift;184float cardboardMaxYShift = pixelHeight_ / 2.0f - cardboardScreenHeight / 2.0f;185float cardboardUserYShift = g_Config.iCardboardYShift / 100.0f * cardboardMaxYShift;186float cardboardScreenY = cardboardMaxYShift + cardboardUserYShift;187188cardboardSettings->enabled = true;189cardboardSettings->leftEyeXPosition = cardboardLeftEyeX;190cardboardSettings->rightEyeXPosition = cardboardRightEyeX;191cardboardSettings->screenYPosition = cardboardScreenY;192cardboardSettings->screenWidth = cardboardScreenWidth;193cardboardSettings->screenHeight = cardboardScreenHeight;194}195196static float GetShaderSettingValue(const ShaderInfo *shaderInfo, int i, const char *nameSuffix) {197std::string key = shaderInfo->section + nameSuffix;198auto it = g_Config.mPostShaderSetting.find(key);199if (it != g_Config.mPostShaderSetting.end())200return it->second;201return shaderInfo->settings[i].value;202}203204void PresentationCommon::CalculatePostShaderUniforms(int bufferWidth, int bufferHeight, int targetWidth, int targetHeight, const ShaderInfo *shaderInfo, PostShaderUniforms *uniforms) const {205float u_delta = 1.0f / bufferWidth;206float v_delta = 1.0f / bufferHeight;207float u_pixel_delta = 1.0f / targetWidth;208float v_pixel_delta = 1.0f / targetHeight;209int flipCount = __DisplayGetFlipCount();210int vCount = __DisplayGetVCount();211float time[4] = { (float)time_now_d(), (vCount % 60) * 1.0f / 60.0f, (float)vCount, (float)(flipCount % 60) };212213uniforms->texelDelta[0] = u_delta;214uniforms->texelDelta[1] = v_delta;215uniforms->pixelDelta[0] = u_pixel_delta;216uniforms->pixelDelta[1] = v_pixel_delta;217memcpy(uniforms->time, time, 4 * sizeof(float));218uniforms->timeDelta[0] = time[0] - previousUniforms_.time[0];219uniforms->timeDelta[1] = (time[2] - previousUniforms_.time[2]) * (1.0f / 60.0f);220uniforms->timeDelta[2] = time[2] - previousUniforms_.time[2];221uniforms->timeDelta[3] = time[3] != previousUniforms_.time[3] ? 1.0f : 0.0f;222uniforms->video = hasVideo_ ? 1.0f : 0.0f;223224// The shader translator tacks this onto our shaders, if we don't set it they render garbage.225uniforms->gl_HalfPixel[0] = u_pixel_delta * 0.5f;226uniforms->gl_HalfPixel[1] = v_pixel_delta * 0.5f;227228uniforms->setting[0] = GetShaderSettingValue(shaderInfo, 0, "SettingCurrentValue1");229uniforms->setting[1] = GetShaderSettingValue(shaderInfo, 1, "SettingCurrentValue2");230uniforms->setting[2] = GetShaderSettingValue(shaderInfo, 2, "SettingCurrentValue3");231uniforms->setting[3] = GetShaderSettingValue(shaderInfo, 3, "SettingCurrentValue4");232}233234static std::string ReadShaderSrc(const Path &filename) {235size_t sz = 0;236char *data = (char *)g_VFS.ReadFile(filename.c_str(), &sz);237if (!data) {238return "";239}240241std::string src(data, sz);242delete[] data;243return src;244}245246// Note: called on resize and settings changes.247// Also takes care of making sure the appropriate stereo shader is compiled.248bool PresentationCommon::UpdatePostShader() {249DestroyStereoShader();250251if (gstate_c.Use(GPU_USE_SIMPLE_STEREO_PERSPECTIVE)) {252const ShaderInfo *stereoShaderInfo = GetPostShaderInfo(g_Config.sStereoToMonoShader);253if (stereoShaderInfo) {254bool result = CompilePostShader(stereoShaderInfo, &stereoPipeline_);255if (result) {256stereoShaderInfo_ = new ShaderInfo(*stereoShaderInfo);257}258} else {259WARN_LOG(Log::G3D, "Failed to get info about stereo shader '%s'", g_Config.sStereoToMonoShader.c_str());260}261}262263std::vector<const ShaderInfo *> shaderInfo;264if (!g_Config.vPostShaderNames.empty()) {265ReloadAllPostShaderInfo(draw_);266shaderInfo = GetFullPostShadersChain(g_Config.vPostShaderNames);267}268269DestroyPostShader();270if (shaderInfo.empty()) {271usePostShader_ = false;272return false;273}274275bool usePreviousFrame = false;276bool usePreviousAtOutputResolution = false;277for (size_t i = 0; i < shaderInfo.size(); ++i) {278const ShaderInfo *next = i + 1 < shaderInfo.size() ? shaderInfo[i + 1] : nullptr;279Draw::Pipeline *postPipeline = nullptr;280if (!BuildPostShader(shaderInfo[i], next, &postPipeline)) {281DestroyPostShader();282return false;283}284_dbg_assert_(postPipeline);285postShaderPipelines_.push_back(postPipeline);286postShaderInfo_.push_back(*shaderInfo[i]);287if (shaderInfo[i]->usePreviousFrame) {288usePreviousFrame = true;289usePreviousAtOutputResolution = shaderInfo[i]->outputResolution;290}291}292293if (usePreviousFrame) {294int w = usePreviousAtOutputResolution ? pixelWidth_ : renderWidth_;295int h = usePreviousAtOutputResolution ? pixelHeight_ : renderHeight_;296297_dbg_assert_(w > 0 && h > 0);298299static constexpr int FRAMES = 2;300previousFramebuffers_.resize(FRAMES);301previousIndex_ = 0;302303for (int i = 0; i < FRAMES; ++i) {304previousFramebuffers_[i] = draw_->CreateFramebuffer({ w, h, 1, 1, 0, false, "inter_presentation" });305if (!previousFramebuffers_[i]) {306DestroyPostShader();307return false;308}309}310}311312usePostShader_ = true;313return true;314}315316bool PresentationCommon::CompilePostShader(const ShaderInfo *shaderInfo, Draw::Pipeline **outPipeline) const {317_assert_(shaderInfo);318319std::string vsSourceGLSL = ReadShaderSrc(shaderInfo->vertexShaderFile);320std::string fsSourceGLSL = ReadShaderSrc(shaderInfo->fragmentShaderFile);321if (vsSourceGLSL.empty() || fsSourceGLSL.empty()) {322return false;323}324325std::string vsError;326std::string fsError;327328// All post shaders are written in GLSL 1.0 so that's what we pass in here as a "from" language.329Draw::ShaderModule *vs = CompileShaderModule(ShaderStage::Vertex, GLSL_1xx, vsSourceGLSL, &vsError);330Draw::ShaderModule *fs = CompileShaderModule(ShaderStage::Fragment, GLSL_1xx, fsSourceGLSL, &fsError);331332// Don't worry, CompileShaderModule makes sure they get freed if one succeeded.333if (!fs || !vs) {334std::string errorString = vsError + "\n" + fsError;335// DO NOT turn this into an ERROR_LOG_REPORT, as it will pollute our logs with all kinds of336// user shader experiments.337ERROR_LOG(Log::FrameBuf, "Failed to build post-processing program from %s and %s!\n%s", shaderInfo->vertexShaderFile.c_str(), shaderInfo->fragmentShaderFile.c_str(), errorString.c_str());338ShowPostShaderError(errorString);339return false;340}341342UniformBufferDesc postShaderDesc{ sizeof(PostShaderUniforms), {343{ "gl_HalfPixel", 0, -1, UniformType::FLOAT4, offsetof(PostShaderUniforms, gl_HalfPixel) },344{ "u_texelDelta", 1, 1, UniformType::FLOAT2, offsetof(PostShaderUniforms, texelDelta) },345{ "u_pixelDelta", 2, 2, UniformType::FLOAT2, offsetof(PostShaderUniforms, pixelDelta) },346{ "u_time", 3, 3, UniformType::FLOAT4, offsetof(PostShaderUniforms, time) },347{ "u_timeDelta", 4, 4, UniformType::FLOAT4, offsetof(PostShaderUniforms, timeDelta) },348{ "u_setting", 5, 5, UniformType::FLOAT4, offsetof(PostShaderUniforms, setting) },349{ "u_video", 6, 6, UniformType::FLOAT1, offsetof(PostShaderUniforms, video) },350} };351352Draw::Pipeline *pipeline = CreatePipeline({ vs, fs }, true, &postShaderDesc);353354fs->Release();355vs->Release();356357if (!pipeline)358return false;359360*outPipeline = pipeline;361return true;362}363364bool PresentationCommon::BuildPostShader(const ShaderInfo * shaderInfo, const ShaderInfo * next, Draw::Pipeline **outPipeline) {365if (!CompilePostShader(shaderInfo, outPipeline)) {366return false;367}368369if (!shaderInfo->outputResolution || next) {370int nextWidth = renderWidth_;371int nextHeight = renderHeight_;372373// When chaining, we use the previous resolution as a base, rather than the render resolution.374if (!postShaderFramebuffers_.empty())375draw_->GetFramebufferDimensions(postShaderFramebuffers_.back(), &nextWidth, &nextHeight);376377if (next && next->isUpscalingFilter) {378// Force 1x for this shader, so the next can upscale.379const bool isPortrait = g_Config.IsPortrait();380nextWidth = isPortrait ? 272 : 480;381nextHeight = isPortrait ? 480 : 272;382} else if (next && next->SSAAFilterLevel >= 2) {383// Increase the resolution this shader outputs for the next to SSAA.384nextWidth *= next->SSAAFilterLevel;385nextHeight *= next->SSAAFilterLevel;386} else if (shaderInfo->outputResolution) {387// If the current shader uses output res (not next), we will use output res for it.388FRect rc;389FRect frame = GetScreenFrame((float)pixelWidth_, (float)pixelHeight_);390CalculateDisplayOutputRect(&rc, 480.0f, 272.0f, frame, g_Config.iInternalScreenRotation);391nextWidth = (int)rc.w;392nextHeight = (int)rc.h;393}394395if (!AllocateFramebuffer(nextWidth, nextHeight)) {396(*outPipeline)->Release();397*outPipeline = nullptr;398return false;399}400}401402return true;403}404405bool PresentationCommon::AllocateFramebuffer(int w, int h) {406using namespace Draw;407408// First, let's try to find a framebuffer of the right size that is NOT the most recent.409Framebuffer *last = postShaderFramebuffers_.empty() ? nullptr : postShaderFramebuffers_.back();410for (const auto &prev : postShaderFBOUsage_) {411if (prev.w == w && prev.h == h && prev.fbo != last) {412// Great, this one's perfect. Ref it for when we release.413prev.fbo->AddRef();414postShaderFramebuffers_.push_back(prev.fbo);415return true;416}417}418419// No depth/stencil for post processing420Draw::Framebuffer *fbo = draw_->CreateFramebuffer({ w, h, 1, 1, 0, false, "presentation" });421if (!fbo) {422return false;423}424425postShaderFBOUsage_.push_back({ fbo, w, h });426postShaderFramebuffers_.push_back(fbo);427return true;428}429430void PresentationCommon::ShowPostShaderError(const std::string &errorString) {431// let's show the first line of the error string as an OSM.432std::set<std::string> blacklistedLines;433// These aren't useful to show, skip to the first interesting line.434blacklistedLines.insert("Fragment shader failed to compile with the following errors:");435blacklistedLines.insert("Vertex shader failed to compile with the following errors:");436blacklistedLines.insert("Compile failed.");437blacklistedLines.insert("");438439std::string firstLine;440size_t start = 0;441for (size_t i = 0; i < errorString.size(); i++) {442if (errorString[i] == '\n' && i == start) {443start = i + 1;444} else if (errorString[i] == '\n') {445firstLine = errorString.substr(start, i - start);446if (blacklistedLines.find(firstLine) == blacklistedLines.end()) {447break;448}449start = i + 1;450firstLine.clear();451}452}453if (!firstLine.empty()) {454g_OSD.Show(OSDType::MESSAGE_ERROR_DUMP, "Post-shader error: " + firstLine + "...:\n" + errorString, 10.0f);455} else {456g_OSD.Show(OSDType::MESSAGE_ERROR, "Post-shader error, see log for details", 10.0f);457}458}459460void PresentationCommon::DeviceLost() {461DestroyDeviceObjects();462draw_ = nullptr;463}464465void PresentationCommon::DeviceRestore(Draw::DrawContext *draw) {466draw_ = draw;467CreateDeviceObjects();468}469470Draw::Pipeline *PresentationCommon::CreatePipeline(std::vector<Draw::ShaderModule *> shaders, bool postShader, const UniformBufferDesc *uniformDesc) const {471using namespace Draw;472473Semantic pos = SEM_POSITION;474Semantic tc = SEM_TEXCOORD0;475// Shader translation marks these both as "TEXCOORDs" on HLSL...476if (postShader && (lang_ == HLSL_D3D11 || lang_ == HLSL_D3D9)) {477pos = SEM_TEXCOORD0;478tc = SEM_TEXCOORD1;479}480481// TODO: Maybe get rid of color0.482InputLayoutDesc inputDesc = {483sizeof(Vertex),484{485{ pos, DataFormat::R32G32B32_FLOAT, 0 },486{ tc, DataFormat::R32G32_FLOAT, 12 },487{ SEM_COLOR0, DataFormat::R8G8B8A8_UNORM, 20 },488},489};490491InputLayout *inputLayout = draw_->CreateInputLayout(inputDesc);492DepthStencilState *depth = draw_->CreateDepthStencilState({ false, false, Comparison::LESS });493BlendState *blendstateOff = draw_->CreateBlendState({ false, 0xF });494RasterState *rasterNoCull = draw_->CreateRasterState({});495496PipelineDesc pipelineDesc{ Primitive::TRIANGLE_STRIP, shaders, inputLayout, depth, blendstateOff, rasterNoCull, uniformDesc };497Pipeline *pipeline = draw_->CreateGraphicsPipeline(pipelineDesc, "presentation");498499inputLayout->Release();500depth->Release();501blendstateOff->Release();502rasterNoCull->Release();503504return pipeline;505}506507void PresentationCommon::CreateDeviceObjects() {508using namespace Draw;509_assert_(vdata_ == nullptr);510511// TODO: Could probably just switch to DrawUP, it's supported well by all backends now.512vdata_ = draw_->CreateBuffer(sizeof(Vertex) * 12, BufferUsageFlag::DYNAMIC | BufferUsageFlag::VERTEXDATA);513514samplerNearest_ = draw_->CreateSamplerState({ TextureFilter::NEAREST, TextureFilter::NEAREST, TextureFilter::NEAREST, 0.0f, TextureAddressMode::CLAMP_TO_EDGE, TextureAddressMode::CLAMP_TO_EDGE, TextureAddressMode::CLAMP_TO_EDGE });515samplerLinear_ = draw_->CreateSamplerState({ TextureFilter::LINEAR, TextureFilter::LINEAR, TextureFilter::LINEAR, 0.0f, TextureAddressMode::CLAMP_TO_EDGE, TextureAddressMode::CLAMP_TO_EDGE, TextureAddressMode::CLAMP_TO_EDGE });516517texColor_ = CreatePipeline({ draw_->GetVshaderPreset(VS_TEXTURE_COLOR_2D), draw_->GetFshaderPreset(FS_TEXTURE_COLOR_2D) }, false, &vsTexColBufDesc);518texColorRBSwizzle_ = CreatePipeline({ draw_->GetVshaderPreset(VS_TEXTURE_COLOR_2D), draw_->GetFshaderPreset(FS_TEXTURE_COLOR_2D_RB_SWIZZLE) }, false, &vsTexColBufDesc);519520if (restorePostShader_)521UpdatePostShader();522restorePostShader_ = false;523}524525template <typename T>526static void DoRelease(T *&obj) {527if (obj)528obj->Release();529obj = nullptr;530}531532template <typename T>533static void DoReleaseVector(std::vector<T *> &list) {534for (auto &obj : list)535obj->Release();536list.clear();537}538539void PresentationCommon::DestroyDeviceObjects() {540DoRelease(texColor_);541DoRelease(texColorRBSwizzle_);542DoRelease(samplerNearest_);543DoRelease(samplerLinear_);544DoRelease(vdata_);545DoRelease(srcTexture_);546DoRelease(srcFramebuffer_);547548restorePostShader_ = usePostShader_;549DestroyPostShader();550DestroyStereoShader();551}552553void PresentationCommon::DestroyPostShader() {554usePostShader_ = false;555556DoReleaseVector(postShaderPipelines_);557DoReleaseVector(postShaderFramebuffers_);558DoReleaseVector(previousFramebuffers_);559postShaderInfo_.clear();560postShaderFBOUsage_.clear();561}562563void PresentationCommon::DestroyStereoShader() {564DoRelease(stereoPipeline_);565delete stereoShaderInfo_;566stereoShaderInfo_ = nullptr;567}568569Draw::ShaderModule *PresentationCommon::CompileShaderModule(ShaderStage stage, ShaderLanguage lang, const std::string &src, std::string *errorString) const {570std::string translated = src;571if (lang != lang_) {572// Gonna have to upconvert the shader.573if (!TranslateShader(&translated, lang_, draw_->GetShaderLanguageDesc(), nullptr, src, lang, stage, errorString)) {574ERROR_LOG(Log::FrameBuf, "Failed to translate post-shader. Error string: '%s'\nSource code:\n%s\n", errorString->c_str(), src.c_str());575return nullptr;576}577}578Draw::ShaderModule *shader = draw_->CreateShaderModule(stage, lang_, (const uint8_t *)translated.c_str(), translated.size(), "postshader");579return shader;580}581582void PresentationCommon::SourceTexture(Draw::Texture *texture, int bufferWidth, int bufferHeight) {583// AddRef before release and assign in case it's the same.584texture->AddRef();585586DoRelease(srcTexture_);587DoRelease(srcFramebuffer_);588589srcTexture_ = texture;590srcWidth_ = bufferWidth;591srcHeight_ = bufferHeight;592}593594void PresentationCommon::SourceFramebuffer(Draw::Framebuffer *fb, int bufferWidth, int bufferHeight) {595fb->AddRef();596597DoRelease(srcTexture_);598DoRelease(srcFramebuffer_);599600srcFramebuffer_ = fb;601srcWidth_ = bufferWidth;602srcHeight_ = bufferHeight;603}604605// Return value is if stereo binding succeeded.606bool PresentationCommon::BindSource(int binding, bool bindStereo) {607if (srcTexture_) {608draw_->BindTexture(binding, srcTexture_);609return false;610} else if (srcFramebuffer_) {611if (bindStereo) {612if (srcFramebuffer_->Layers() > 1) {613draw_->BindFramebufferAsTexture(srcFramebuffer_, binding, Draw::FB_COLOR_BIT, Draw::ALL_LAYERS);614return true;615} else {616// Single layer. This might be from a post shader and those don't yet support stereo.617draw_->BindFramebufferAsTexture(srcFramebuffer_, binding, Draw::FB_COLOR_BIT, 0);618return false;619}620} else {621draw_->BindFramebufferAsTexture(srcFramebuffer_, binding, Draw::FB_COLOR_BIT, 0);622return false;623}624} else {625_assert_(false);626return false;627}628}629630void PresentationCommon::UpdateUniforms(bool hasVideo) {631hasVideo_ = hasVideo;632}633634void PresentationCommon::CopyToOutput(OutputFlags flags, int uvRotation, float u0, float v0, float u1, float v1) {635draw_->Invalidate(InvalidationFlags::CACHED_RENDER_STATE);636637// TODO: If shader objects have been created by now, we might have received errors.638// GLES can have the shader fail later, shader->failed / shader->error.639// This should auto-disable usePostShader_ and call ShowPostShaderError().640641bool useNearest = flags & OutputFlags::NEAREST;642bool useStereo = gstate_c.Use(GPU_USE_SIMPLE_STEREO_PERSPECTIVE) && stereoPipeline_ != nullptr; // TODO: Also check that the backend has support for it.643644const bool usePostShader = usePostShader_ && !useStereo && !(flags & OutputFlags::RB_SWIZZLE);645const bool isFinalAtOutputResolution = usePostShader && postShaderFramebuffers_.size() < postShaderPipelines_.size();646Draw::Framebuffer *postShaderOutput = nullptr;647int lastWidth = srcWidth_;648int lastHeight = srcHeight_;649650int pixelWidth = pixelWidth_;651int pixelHeight = pixelHeight_;652653// These are the output coordinates.654FRect frame = GetScreenFrame((float)pixelWidth, (float)pixelHeight);655// Note: In cardboard mode, we halve the width here to compensate656// for splitting the window in half, while still reusing normal centering.657if (g_Config.bEnableCardboardVR) {658frame.w /= 2.0;659pixelWidth /= 2;660}661FRect rc;662CalculateDisplayOutputRect(&rc, 480.0f, 272.0f, frame, uvRotation);663664if (GetGPUBackend() == GPUBackend::DIRECT3D9) {665rc.x -= 0.5f;666// This is plus because the top is larger y.667rc.y += 0.5f;668}669670// To make buffer updates easier, we use one array of verts.671int postVertsOffset = (int)sizeof(Vertex) * 4;672673float finalU0 = u0, finalU1 = u1, finalV0 = v0, finalV1 = v1;674675if (usePostShader && !(isFinalAtOutputResolution && postShaderPipelines_.size() == 1)) {676// The final blit will thus use the full texture.677finalU0 = 0.0f;678finalV0 = 0.0f;679finalU1 = 1.0f;680finalV1 = 1.0f;681}682683// Our vertex buffer is split into three parts, with four vertices each:684// 0-3: The final blit vertices (needs to handle cropping the input ONLY if post-processing is not enabled)685// 4-7: Post-processing, other passes686// 8-11: Post-processing, first pass (needs to handle cropping the input image, if wrong dimensions)687Vertex verts[12] = {688{ rc.x, rc.y, 0, finalU0, finalV0, 0xFFFFFFFF }, // TL689{ rc.x + rc.w, rc.y, 0, finalU1, finalV0, 0xFFFFFFFF }, // TR690{ rc.x, rc.y + rc.h, 0, finalU0, finalV1, 0xFFFFFFFF }, // BL691{ rc.x + rc.w, rc.y + rc.h, 0, finalU1, finalV1, 0xFFFFFFFF }, // BR692};693694// Rescale X, Y to normalized coordinate system.695float invDestW = 2.0f / pixelWidth;696float invDestH = 2.0f / pixelHeight;697for (int i = 0; i < 4; i++) {698verts[i].x = verts[i].x * invDestW - 1.0f;699verts[i].y = verts[i].y * invDestH - 1.0f;700}701702if (uvRotation != ROTATION_LOCKED_HORIZONTAL) {703struct {704float u;705float v;706} temp[4];707int rotation = 0;708// Vertical and Vertical180 needed swapping after we changed the coordinate system.709switch (uvRotation) {710case ROTATION_LOCKED_HORIZONTAL180: rotation = 2; break;711case ROTATION_LOCKED_VERTICAL: rotation = 3; break;712case ROTATION_LOCKED_VERTICAL180: rotation = 1; break;713}714715// If we flipped, we rotate the other way.716if ((flags & OutputFlags::BACKBUFFER_FLIPPED) || (flags & OutputFlags::POSITION_FLIPPED)) {717if ((rotation & 1) != 0)718rotation ^= 2;719}720721static int rotLookup[4] = { 0, 1, 3, 2 };722723for (int i = 0; i < 4; i++) {724int otherI = rotLookup[(rotLookup[i] + rotation) & 3];725temp[i].u = verts[otherI].u;726temp[i].v = verts[otherI].v;727}728for (int i = 0; i < 4; i++) {729verts[i].u = temp[i].u;730verts[i].v = temp[i].v;731}732}733734if (isFinalAtOutputResolution || useStereo) {735// In this mode, we ignore the g_display_rot_matrix. Apply manually.736if (g_display.rotation != DisplayRotation::ROTATE_0) {737for (int i = 0; i < 4; i++) {738Lin::Vec3 v(verts[i].x, verts[i].y, verts[i].z);739// Backwards notation, should fix that...740v = v * g_display.rot_matrix;741verts[i].x = v.x;742verts[i].y = v.y;743}744}745}746747if (flags & OutputFlags::PILLARBOX) {748for (int i = 0; i < 4; i++) {749// Looks about right.750verts[i].x *= 0.75f;751}752}753754// Finally, we compensate the y vertex positions for the backbuffer for any flipping.755if ((flags & OutputFlags::POSITION_FLIPPED) || (flags & OutputFlags::BACKBUFFER_FLIPPED)) {756for (int i = 0; i < 4; i++) {757verts[i].y = -verts[i].y;758}759}760761// Grab the previous framebuffer early so we can change previousIndex_ when we want.762Draw::Framebuffer *previousFramebuffer = previousFramebuffers_.empty() ? nullptr : previousFramebuffers_[previousIndex_];763764PostShaderUniforms uniforms;765const auto performShaderPass = [&](const ShaderInfo *shaderInfo, Draw::Framebuffer *postShaderFramebuffer, Draw::Pipeline *postShaderPipeline, int vertsOffset) {766if (postShaderOutput) {767draw_->BindFramebufferAsTexture(postShaderOutput, 0, Draw::FB_COLOR_BIT, 0);768} else {769BindSource(0, false);770}771BindSource(1, false);772if (shaderInfo->usePreviousFrame)773draw_->BindFramebufferAsTexture(previousFramebuffer, 2, Draw::FB_COLOR_BIT, 0);774775int nextWidth, nextHeight;776draw_->GetFramebufferDimensions(postShaderFramebuffer, &nextWidth, &nextHeight);777Draw::Viewport viewport{ 0, 0, (float)nextWidth, (float)nextHeight, 0.0f, 1.0f };778draw_->SetViewport(viewport);779draw_->SetScissorRect(0, 0, nextWidth, nextHeight);780781CalculatePostShaderUniforms(lastWidth, lastHeight, nextWidth, nextHeight, shaderInfo, &uniforms);782783draw_->BindPipeline(postShaderPipeline);784draw_->UpdateDynamicUniformBuffer(&uniforms, sizeof(uniforms));785786Draw::SamplerState *sampler = useNearest || shaderInfo->isUpscalingFilter ? samplerNearest_ : samplerLinear_;787draw_->BindSamplerStates(0, 1, &sampler);788draw_->BindSamplerStates(1, 1, &sampler);789if (shaderInfo->usePreviousFrame)790draw_->BindSamplerStates(2, 1, &sampler);791792draw_->BindVertexBuffer(vdata_, vertsOffset);793draw_->Draw(4, 0);794795postShaderOutput = postShaderFramebuffer;796lastWidth = nextWidth;797lastHeight = nextHeight;798};799800if (usePostShader) {801// When we render to temp framebuffers during post, we switch position, not UV.802// The flipping here is only because D3D has a clip coordinate system that doesn't match their screen coordinate system.803// The flipping here is only because D3D has a clip coordinate system that doesn't match their screen coordinate system.804bool flipped = flags & OutputFlags::POSITION_FLIPPED;805float y0 = flipped ? 1.0f : -1.0f;806float y1 = flipped ? -1.0f : 1.0f;807verts[4] = { -1.0f, y0, 0.0f, 0.0f, 0.0f, 0xFFFFFFFF }; // TL808verts[5] = { 1.0f, y0, 0.0f, 1.0f, 0.0f, 0xFFFFFFFF }; // TR809verts[6] = { -1.0f, y1, 0.0f, 0.0f, 1.0f, 0xFFFFFFFF }; // BL810verts[7] = { 1.0f, y1, 0.0f, 1.0f, 1.0f, 0xFFFFFFFF }; // BR811812// Now, adjust for the desired input rectangle.813verts[8] = { -1.0f, y0, 0.0f, u0, v0, 0xFFFFFFFF }; // TL814verts[9] = { 1.0f, y0, 0.0f, u1, v0, 0xFFFFFFFF }; // TR815verts[10] = { -1.0f, y1, 0.0f, u0, v1, 0xFFFFFFFF }; // BL816verts[11] = { 1.0f, y1, 0.0f, u1, v1, 0xFFFFFFFF }; // BR817818draw_->UpdateBuffer(vdata_, (const uint8_t *)verts, 0, sizeof(verts), Draw::UPDATE_DISCARD);819820for (size_t i = 0; i < postShaderFramebuffers_.size(); ++i) {821Draw::Pipeline *postShaderPipeline = postShaderPipelines_[i];822const ShaderInfo *shaderInfo = &postShaderInfo_[i];823Draw::Framebuffer *postShaderFramebuffer = postShaderFramebuffers_[i];824if (!isFinalAtOutputResolution && i == postShaderFramebuffers_.size() - 1 && !previousFramebuffers_.empty()) {825// This is the last pass and we're going direct to the backbuffer after this.826// Redirect output to a separate framebuffer to keep the previous frame.827previousIndex_++;828if (previousIndex_ >= (int)previousFramebuffers_.size())829previousIndex_ = 0;830postShaderFramebuffer = previousFramebuffers_[previousIndex_];831}832833draw_->BindFramebufferAsRenderTarget(postShaderFramebuffer, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "PostShader");834835// Pick vertices 8-11 for the first pass.836int vertOffset = i == 0 ? (int)sizeof(Vertex) * 8 : (int)sizeof(Vertex) * 4;837performShaderPass(shaderInfo, postShaderFramebuffer, postShaderPipeline, vertOffset);838}839840if (isFinalAtOutputResolution && postShaderInfo_.back().isUpscalingFilter)841useNearest = true;842} else {843// Only need to update the first four verts, the rest are unused.844draw_->UpdateBuffer(vdata_, (const uint8_t *)verts, 0, postVertsOffset, Draw::UPDATE_DISCARD);845}846847// If we need to save the previous frame, we have to save any final pass in a framebuffer.848if (isFinalAtOutputResolution && !previousFramebuffers_.empty()) {849Draw::Pipeline *postShaderPipeline = postShaderPipelines_.back();850const ShaderInfo *shaderInfo = &postShaderInfo_.back();851852// Pick the next to render to.853previousIndex_++;854if (previousIndex_ >= (int)previousFramebuffers_.size())855previousIndex_ = 0;856Draw::Framebuffer *postShaderFramebuffer = previousFramebuffers_[previousIndex_];857858draw_->BindFramebufferAsRenderTarget(postShaderFramebuffer, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "InterFrameBlit");859performShaderPass(shaderInfo, postShaderFramebuffer, postShaderPipeline, postVertsOffset);860}861862draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "FinalBlit");863draw_->SetScissorRect(0, 0, pixelWidth_, pixelHeight_);864865Draw::Pipeline *pipeline = (flags & OutputFlags::RB_SWIZZLE) ? texColorRBSwizzle_ : texColor_;866867if (useStereo) {868draw_->BindPipeline(stereoPipeline_);869if (!BindSource(0, true)) {870// Fall back871draw_->BindPipeline(texColor_);872useStereo = false; // Otherwise we end up uploading the wrong uniforms873}874} else {875if (isFinalAtOutputResolution && previousFramebuffers_.empty()) {876pipeline = postShaderPipelines_.back();877}878879draw_->BindPipeline(pipeline);880if (postShaderOutput) {881draw_->BindFramebufferAsTexture(postShaderOutput, 0, Draw::FB_COLOR_BIT, 0);882} else {883BindSource(0, false);884}885}886BindSource(1, false);887888if (isFinalAtOutputResolution && previousFramebuffers_.empty()) {889CalculatePostShaderUniforms(lastWidth, lastHeight, (int)rc.w, (int)rc.h, &postShaderInfo_.back(), &uniforms);890draw_->UpdateDynamicUniformBuffer(&uniforms, sizeof(uniforms));891} else if (useStereo) {892CalculatePostShaderUniforms(lastWidth, lastHeight, (int)rc.w, (int)rc.h, stereoShaderInfo_, &uniforms);893draw_->UpdateDynamicUniformBuffer(&uniforms, sizeof(uniforms));894} else {895Draw::VsTexColUB ub{};896memcpy(ub.WorldViewProj, g_display.rot_matrix.m, sizeof(float) * 16);897draw_->UpdateDynamicUniformBuffer(&ub, sizeof(ub));898}899900draw_->BindVertexBuffer(vdata_, 0);901902Draw::SamplerState *sampler = useNearest ? samplerNearest_ : samplerLinear_;903draw_->BindSamplerStates(0, 1, &sampler);904draw_->BindSamplerStates(1, 1, &sampler);905906auto setViewport = [&](float x, float y, float w, float h) {907Draw::Viewport viewport{ x, y, w, h, 0.0f, 1.0f };908draw_->SetViewport(viewport);909};910911CardboardSettings cardboardSettings;912GetCardboardSettings(&cardboardSettings);913if (cardboardSettings.enabled) {914// TODO: This could actually support stereo now, with an appropriate shader.915916// This is what the left eye sees.917setViewport(cardboardSettings.leftEyeXPosition, cardboardSettings.screenYPosition, cardboardSettings.screenWidth, cardboardSettings.screenHeight);918draw_->Draw(4, 0);919920// And this is the right eye, unless they're a pirate.921setViewport(cardboardSettings.rightEyeXPosition, cardboardSettings.screenYPosition, cardboardSettings.screenWidth, cardboardSettings.screenHeight);922draw_->Draw(4, 0);923} else {924setViewport(0.0f, 0.0f, (float)pixelWidth_, (float)pixelHeight_);925draw_->Draw(4, 0);926}927928DoRelease(srcFramebuffer_);929DoRelease(srcTexture_);930931// Unbinds all textures and samplers too, needed since sometimes a MakePixelTexture is deleted etc.932draw_->Invalidate(InvalidationFlags::CACHED_RENDER_STATE);933934previousUniforms_ = uniforms;935presentedThisFrame_ = true;936}937938void PresentationCommon::CalculateRenderResolution(int *width, int *height, int *scaleFactor, bool *upscaling, bool *ssaa) const {939// Check if postprocessing shader is doing upscaling as it requires native resolution940std::vector<const ShaderInfo *> shaderInfo;941if (!g_Config.vPostShaderNames.empty()) {942ReloadAllPostShaderInfo(draw_);943RemoveUnknownPostShaders(&g_Config.vPostShaderNames);944FixPostShaderOrder(&g_Config.vPostShaderNames);945shaderInfo = GetFullPostShadersChain(g_Config.vPostShaderNames);946}947948bool firstIsUpscalingFilter = shaderInfo.empty() ? false : shaderInfo.front()->isUpscalingFilter;949int firstSSAAFilterLevel = shaderInfo.empty() ? 0 : shaderInfo.front()->SSAAFilterLevel;950951// In auto mode (zoom == 0), round up to an integer zoom factor for the render size.952int zoom = g_Config.iInternalResolution;953if (zoom == 0 || firstSSAAFilterLevel >= 2) {954// auto mode, use the longest dimension955if (!g_Config.IsPortrait()) {956zoom = (PSP_CoreParameter().pixelWidth + 479) / 480;957} else {958zoom = (PSP_CoreParameter().pixelHeight + 479) / 480;959}960if (firstSSAAFilterLevel >= 2)961zoom *= firstSSAAFilterLevel;962}963if (zoom <= 1 || firstIsUpscalingFilter)964zoom = 1;965966if (upscaling) {967*upscaling = firstIsUpscalingFilter;968for (auto &info : shaderInfo) {969*upscaling = *upscaling || info->isUpscalingFilter;970}971}972if (ssaa) {973*ssaa = firstSSAAFilterLevel >= 2;974for (auto &info : shaderInfo) {975*ssaa = *ssaa || info->SSAAFilterLevel >= 2;976}977}978979if (IsVREnabled()) {980*width = 480 * zoom;981*height = 480 * zoom;982} else {983// Note: We previously checked g_Config.IsPortrait (internal rotation) here but that was wrong -984// we still render at 480x272 * zoom.985*width = 480 * zoom;986*height = 272 * zoom;987}988989*scaleFactor = zoom;990}991992993