Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/GPU/Common/PresentationCommon.cpp
3187 views
1
// Copyright (c) 2012- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include <algorithm>
19
#include <cmath>
20
#include <set>
21
#include <cstdint>
22
#include "Common/GPU/thin3d.h"
23
24
#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/Math/geom2d.h"
30
#include "Common/Log.h"
31
#include "Common/TimeUtil.h"
32
#include "Core/Config.h"
33
#include "Core/ConfigValues.h"
34
#include "Core/System.h"
35
#include "Core/HW/Display.h"
36
#include "GPU/Common/PostShader.h"
37
#include "GPU/Common/PresentationCommon.h"
38
#include "GPU/GPUState.h"
39
#include "Common/GPU/ShaderTranslation.h"
40
41
struct Vertex {
42
float x, y, z;
43
float u, v;
44
uint32_t rgba;
45
};
46
47
static bool g_overrideScreenBounds;
48
static Bounds g_screenBounds;
49
50
void SetOverrideScreenFrame(const Bounds *bounds) {
51
g_overrideScreenBounds = bounds != nullptr;
52
if (bounds) {
53
g_screenBounds = *bounds;
54
}
55
}
56
57
FRect GetScreenFrame(float pixelWidth, float pixelHeight) {
58
FRect rc = FRect{
59
0.0f,
60
0.0f,
61
pixelWidth,
62
pixelHeight,
63
};
64
65
bool applyInset = !g_Config.bIgnoreScreenInsets;
66
67
if (applyInset) {
68
// Remove the DPI scale to get back to pixels.
69
float left = System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_LEFT) / g_display.dpi_scale_x;
70
float right = System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_RIGHT) / g_display.dpi_scale_x;
71
float top = System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_TOP) / g_display.dpi_scale_y;
72
float bottom = System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_BOTTOM) / g_display.dpi_scale_y;
73
74
// Adjust left edge to compensate for cutouts (notches) if any.
75
rc.x += left;
76
rc.w -= (left + right);
77
rc.y += top;
78
rc.h -= (top + bottom);
79
}
80
81
if (g_overrideScreenBounds) {
82
// Set rectangle to match central node. Here we ignore bIgnoreScreenInsets.
83
rc.x = g_screenBounds.x;
84
rc.y = g_screenBounds.y;
85
rc.w = g_screenBounds.w;
86
rc.h = g_screenBounds.h;
87
}
88
89
return rc;
90
}
91
92
void CalculateDisplayOutputRect(FRect *rc, float origW, float origH, const FRect &frame, int rotation) {
93
float outW;
94
float outH;
95
96
bool rotated = rotation == ROTATION_LOCKED_VERTICAL || rotation == ROTATION_LOCKED_VERTICAL180;
97
98
bool stretch = g_Config.bDisplayStretch && !g_Config.bDisplayIntegerScale;
99
100
float offsetX = g_Config.fDisplayOffsetX;
101
float offsetY = g_Config.fDisplayOffsetY;
102
103
float scale = g_Config.fDisplayScale;
104
float aspectRatioAdjust = g_Config.fDisplayAspectRatio;
105
106
float origRatio = !rotated ? origW / origH : origH / origW;
107
float frameRatio = frame.w / frame.h;
108
109
if (stretch) {
110
// Automatically set aspect ratio to match the display, IF the rotation matches the output display ratio! Otherwise, just
111
// sets standard aspect ratio because actually stretching will just look silly.
112
bool globalRotated = g_display.rotation == DisplayRotation::ROTATE_90 || g_display.rotation == DisplayRotation::ROTATE_270;
113
if (rotated == (g_display.dp_yres > g_display.dp_xres)) {
114
origRatio = frameRatio;
115
} else {
116
origRatio *= aspectRatioAdjust;
117
}
118
} else {
119
origRatio *= aspectRatioAdjust;
120
}
121
122
float scaledWidth = frame.w * scale;
123
float scaledHeight = frame.h * scale;
124
125
if (origRatio > frameRatio) {
126
// Image is wider than frame. Center vertically.
127
outW = scaledWidth;
128
outH = scaledWidth / origRatio;
129
} else {
130
// Image is taller than frame. Center horizontally.
131
outW = scaledHeight * origRatio;
132
outH = scaledHeight;
133
}
134
135
// Ye olde 1080p hack: If everything is setup to exactly cover the screen (defaults), and the screen display aspect ratio is 16:9,
136
// cut off one line from the top and bottom.
137
if (scale == 1.0f && aspectRatioAdjust == 1.0f && offsetX == 0.5f && offsetY == 0.5f && g_Config.bDisplayCropTo16x9) {
138
if (fabsf(frame.w / frame.h - 16.0f / 9.0f) < 0.0001f) {
139
outW *= 272.0f / 270.0f;
140
outH *= 272.0f / 270.0f;
141
}
142
}
143
144
if (g_Config.bDisplayIntegerScale) {
145
float wDim = 480.0f;
146
if (rotated) {
147
wDim = 272.0f;
148
}
149
150
int zoom = g_Config.iInternalResolution;
151
if (zoom == 0) {
152
// Auto (1:1) mode, not super meaningful with integer scaling, but let's do something that makes
153
// some sense. use the longest dimension, just to have something. round down.
154
if (!g_Config.IsPortrait()) {
155
zoom = (PSP_CoreParameter().pixelWidth) / 480;
156
} else {
157
zoom = (PSP_CoreParameter().pixelHeight) / 480;
158
}
159
}
160
// If integer scaling, limit ourselves to even multiples of the rendered resolution,
161
// to make sure all the pixels are square.
162
wDim *= zoom;
163
outW = std::max(1.0f, floorf(outW / wDim)) * wDim;
164
outH = outW / origRatio;
165
}
166
167
if (IsVREnabled()) {
168
rc->x = 0;
169
rc->y = 0;
170
rc->w = floorf(frame.w);
171
rc->h = floorf(frame.h);
172
outW = frame.w;
173
outH = frame.h;
174
} else {
175
rc->x = floorf(frame.x + frame.w * offsetX - outW * 0.5f);
176
rc->y = floorf(frame.y + frame.h * offsetY - outH * 0.5f);
177
rc->w = floorf(outW);
178
rc->h = floorf(outH);
179
}
180
}
181
182
PresentationCommon::PresentationCommon(Draw::DrawContext *draw) : draw_(draw) {
183
CreateDeviceObjects();
184
}
185
186
PresentationCommon::~PresentationCommon() {
187
DestroyDeviceObjects();
188
}
189
190
void PresentationCommon::GetCardboardSettings(CardboardSettings *cardboardSettings) const {
191
if (!g_Config.bEnableCardboardVR) {
192
cardboardSettings->enabled = false;
193
return;
194
}
195
196
// Calculate Cardboard Settings
197
float cardboardScreenScale = g_Config.iCardboardScreenSize / 100.0f;
198
float cardboardScreenWidth = pixelWidth_ / 2.0f * cardboardScreenScale;
199
float cardboardScreenHeight = pixelHeight_ * cardboardScreenScale;
200
float cardboardMaxXShift = (pixelWidth_ / 2.0f - cardboardScreenWidth) / 2.0f;
201
float cardboardUserXShift = g_Config.iCardboardXShift / 100.0f * cardboardMaxXShift;
202
float cardboardLeftEyeX = cardboardMaxXShift + cardboardUserXShift;
203
float cardboardRightEyeX = pixelWidth_ / 2.0f + cardboardMaxXShift - cardboardUserXShift;
204
float cardboardMaxYShift = pixelHeight_ / 2.0f - cardboardScreenHeight / 2.0f;
205
float cardboardUserYShift = g_Config.iCardboardYShift / 100.0f * cardboardMaxYShift;
206
float cardboardScreenY = cardboardMaxYShift + cardboardUserYShift;
207
208
cardboardSettings->enabled = true;
209
cardboardSettings->leftEyeXPosition = cardboardLeftEyeX;
210
cardboardSettings->rightEyeXPosition = cardboardRightEyeX;
211
cardboardSettings->screenYPosition = cardboardScreenY;
212
cardboardSettings->screenWidth = cardboardScreenWidth;
213
cardboardSettings->screenHeight = cardboardScreenHeight;
214
}
215
216
static float GetShaderSettingValue(const ShaderInfo *shaderInfo, int i, const char *nameSuffix) {
217
std::string key = shaderInfo->section + nameSuffix;
218
auto it = g_Config.mPostShaderSetting.find(key);
219
if (it != g_Config.mPostShaderSetting.end())
220
return it->second;
221
return shaderInfo->settings[i].value;
222
}
223
224
void PresentationCommon::CalculatePostShaderUniforms(int bufferWidth, int bufferHeight, int targetWidth, int targetHeight, const ShaderInfo *shaderInfo, PostShaderUniforms *uniforms) const {
225
float u_delta = 1.0f / bufferWidth;
226
float v_delta = 1.0f / bufferHeight;
227
float u_pixel_delta = 1.0f / targetWidth;
228
float v_pixel_delta = 1.0f / targetHeight;
229
int flipCount = __DisplayGetFlipCount();
230
int vCount = __DisplayGetVCount();
231
float time[4] = { (float)time_now_d(), (vCount % 60) * 1.0f / 60.0f, (float)vCount, (float)(flipCount % 60) };
232
233
uniforms->texelDelta[0] = u_delta;
234
uniforms->texelDelta[1] = v_delta;
235
uniforms->pixelDelta[0] = u_pixel_delta;
236
uniforms->pixelDelta[1] = v_pixel_delta;
237
memcpy(uniforms->time, time, 4 * sizeof(float));
238
uniforms->timeDelta[0] = time[0] - previousUniforms_.time[0];
239
uniforms->timeDelta[1] = (time[2] - previousUniforms_.time[2]) * (1.0f / 60.0f);
240
uniforms->timeDelta[2] = time[2] - previousUniforms_.time[2];
241
uniforms->timeDelta[3] = time[3] != previousUniforms_.time[3] ? 1.0f : 0.0f;
242
uniforms->video = hasVideo_ ? 1.0f : 0.0f;
243
uniforms->vr = IsVREnabled() && IsBigScreenVRMode() ? 1.0f : 0.0f;
244
245
// The shader translator tacks this onto our shaders, if we don't set it they render garbage.
246
uniforms->gl_HalfPixel[0] = u_pixel_delta * 0.5f;
247
uniforms->gl_HalfPixel[1] = v_pixel_delta * 0.5f;
248
249
uniforms->setting[0] = GetShaderSettingValue(shaderInfo, 0, "SettingCurrentValue1");
250
uniforms->setting[1] = GetShaderSettingValue(shaderInfo, 1, "SettingCurrentValue2");
251
uniforms->setting[2] = GetShaderSettingValue(shaderInfo, 2, "SettingCurrentValue3");
252
uniforms->setting[3] = GetShaderSettingValue(shaderInfo, 3, "SettingCurrentValue4");
253
}
254
255
static std::string ReadShaderSrc(const Path &filename) {
256
size_t sz = 0;
257
char *data = (char *)g_VFS.ReadFile(filename.c_str(), &sz);
258
if (!data) {
259
return "";
260
}
261
262
std::string src(data, sz);
263
delete[] data;
264
return src;
265
}
266
267
// Note: called on resize and settings changes.
268
// Also takes care of making sure the appropriate stereo shader is compiled.
269
bool PresentationCommon::UpdatePostShader() {
270
DestroyStereoShader();
271
272
if (gstate_c.Use(GPU_USE_SIMPLE_STEREO_PERSPECTIVE)) {
273
const ShaderInfo *stereoShaderInfo = GetPostShaderInfo(g_Config.sStereoToMonoShader);
274
if (stereoShaderInfo) {
275
bool result = CompilePostShader(stereoShaderInfo, &stereoPipeline_);
276
if (result) {
277
stereoShaderInfo_ = new ShaderInfo(*stereoShaderInfo);
278
}
279
} else {
280
WARN_LOG(Log::G3D, "Failed to get info about stereo shader '%s'", g_Config.sStereoToMonoShader.c_str());
281
}
282
}
283
284
std::vector<const ShaderInfo *> shaderInfo;
285
if (!g_Config.vPostShaderNames.empty()) {
286
ReloadAllPostShaderInfo(draw_);
287
shaderInfo = GetFullPostShadersChain(g_Config.vPostShaderNames);
288
}
289
290
DestroyPostShader();
291
if (shaderInfo.empty()) {
292
usePostShader_ = false;
293
return false;
294
}
295
296
bool usePreviousFrame = false;
297
bool usePreviousAtOutputResolution = false;
298
for (size_t i = 0; i < shaderInfo.size(); ++i) {
299
const ShaderInfo *next = i + 1 < shaderInfo.size() ? shaderInfo[i + 1] : nullptr;
300
Draw::Pipeline *postPipeline = nullptr;
301
if (!BuildPostShader(shaderInfo[i], next, &postPipeline)) {
302
DestroyPostShader();
303
return false;
304
}
305
_dbg_assert_(postPipeline);
306
postShaderPipelines_.push_back(postPipeline);
307
postShaderInfo_.push_back(*shaderInfo[i]);
308
if (shaderInfo[i]->usePreviousFrame) {
309
usePreviousFrame = true;
310
usePreviousAtOutputResolution = shaderInfo[i]->outputResolution;
311
}
312
}
313
314
if (usePreviousFrame) {
315
int w = usePreviousAtOutputResolution ? pixelWidth_ : renderWidth_;
316
int h = usePreviousAtOutputResolution ? pixelHeight_ : renderHeight_;
317
318
_dbg_assert_(w > 0 && h > 0);
319
320
static constexpr int FRAMES = 2;
321
previousFramebuffers_.resize(FRAMES);
322
previousIndex_ = 0;
323
324
for (int i = 0; i < FRAMES; ++i) {
325
previousFramebuffers_[i] = draw_->CreateFramebuffer({ w, h, 1, 1, 0, false, "inter_presentation" });
326
if (!previousFramebuffers_[i]) {
327
DestroyPostShader();
328
return false;
329
}
330
}
331
}
332
333
usePostShader_ = true;
334
return true;
335
}
336
337
bool PresentationCommon::CompilePostShader(const ShaderInfo *shaderInfo, Draw::Pipeline **outPipeline) const {
338
_assert_(shaderInfo);
339
340
std::string vsSourceGLSL = ReadShaderSrc(shaderInfo->vertexShaderFile);
341
std::string fsSourceGLSL = ReadShaderSrc(shaderInfo->fragmentShaderFile);
342
if (vsSourceGLSL.empty() || fsSourceGLSL.empty()) {
343
return false;
344
}
345
346
std::string vsError;
347
std::string fsError;
348
349
// All post shaders are written in GLSL 1.0 so that's what we pass in here as a "from" language.
350
Draw::ShaderModule *vs = CompileShaderModule(ShaderStage::Vertex, GLSL_1xx, vsSourceGLSL, &vsError);
351
Draw::ShaderModule *fs = CompileShaderModule(ShaderStage::Fragment, GLSL_1xx, fsSourceGLSL, &fsError);
352
353
// Don't worry, CompileShaderModule makes sure they get freed if one succeeded.
354
if (!fs || !vs) {
355
std::string errorString = vsError + "\n" + fsError;
356
// DO NOT turn this into an ERROR_LOG_REPORT, as it will pollute our logs with all kinds of
357
// user shader experiments.
358
ERROR_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());
359
ShowPostShaderError(errorString);
360
return false;
361
}
362
363
UniformBufferDesc postShaderDesc{ sizeof(PostShaderUniforms), {
364
{ "gl_HalfPixel", 0, -1, UniformType::FLOAT4, offsetof(PostShaderUniforms, gl_HalfPixel) },
365
{ "u_texelDelta", 1, 1, UniformType::FLOAT2, offsetof(PostShaderUniforms, texelDelta) },
366
{ "u_pixelDelta", 2, 2, UniformType::FLOAT2, offsetof(PostShaderUniforms, pixelDelta) },
367
{ "u_time", 3, 3, UniformType::FLOAT4, offsetof(PostShaderUniforms, time) },
368
{ "u_timeDelta", 4, 4, UniformType::FLOAT4, offsetof(PostShaderUniforms, timeDelta) },
369
{ "u_setting", 5, 5, UniformType::FLOAT4, offsetof(PostShaderUniforms, setting) },
370
{ "u_video", 6, 6, UniformType::FLOAT1, offsetof(PostShaderUniforms, video) },
371
{ "u_vr", 7, 7, UniformType::FLOAT1, offsetof(PostShaderUniforms, vr) },
372
} };
373
374
Draw::Pipeline *pipeline = CreatePipeline({ vs, fs }, true, &postShaderDesc);
375
376
fs->Release();
377
vs->Release();
378
379
if (!pipeline)
380
return false;
381
382
*outPipeline = pipeline;
383
return true;
384
}
385
386
bool PresentationCommon::BuildPostShader(const ShaderInfo * shaderInfo, const ShaderInfo * next, Draw::Pipeline **outPipeline) {
387
if (!CompilePostShader(shaderInfo, outPipeline)) {
388
return false;
389
}
390
391
if (!shaderInfo->outputResolution || next) {
392
int nextWidth = renderWidth_;
393
int nextHeight = renderHeight_;
394
395
// When chaining, we use the previous resolution as a base, rather than the render resolution.
396
if (!postShaderFramebuffers_.empty())
397
draw_->GetFramebufferDimensions(postShaderFramebuffers_.back(), &nextWidth, &nextHeight);
398
399
if (next && next->isUpscalingFilter) {
400
// Force 1x for this shader, so the next can upscale.
401
const bool isPortrait = g_Config.IsPortrait();
402
nextWidth = isPortrait ? 272 : 480;
403
nextHeight = isPortrait ? 480 : 272;
404
} else if (next && next->SSAAFilterLevel >= 2) {
405
// Increase the resolution this shader outputs for the next to SSAA.
406
nextWidth *= next->SSAAFilterLevel;
407
nextHeight *= next->SSAAFilterLevel;
408
} else if (shaderInfo->outputResolution) {
409
// If the current shader uses output res (not next), we will use output res for it.
410
FRect rc;
411
FRect frame = GetScreenFrame((float)pixelWidth_, (float)pixelHeight_);
412
CalculateDisplayOutputRect(&rc, 480.0f, 272.0f, frame, g_Config.iInternalScreenRotation);
413
nextWidth = (int)rc.w;
414
nextHeight = (int)rc.h;
415
}
416
417
if (!AllocateFramebuffer(nextWidth, nextHeight)) {
418
(*outPipeline)->Release();
419
*outPipeline = nullptr;
420
return false;
421
}
422
}
423
424
return true;
425
}
426
427
bool PresentationCommon::AllocateFramebuffer(int w, int h) {
428
using namespace Draw;
429
430
// First, let's try to find a framebuffer of the right size that is NOT the most recent.
431
Framebuffer *last = postShaderFramebuffers_.empty() ? nullptr : postShaderFramebuffers_.back();
432
for (const auto &prev : postShaderFBOUsage_) {
433
if (prev.w == w && prev.h == h && prev.fbo != last) {
434
// Great, this one's perfect. Ref it for when we release.
435
prev.fbo->AddRef();
436
postShaderFramebuffers_.push_back(prev.fbo);
437
return true;
438
}
439
}
440
441
// No depth/stencil for post processing
442
Draw::Framebuffer *fbo = draw_->CreateFramebuffer({ w, h, 1, 1, 0, false, "presentation" });
443
if (!fbo) {
444
return false;
445
}
446
447
postShaderFBOUsage_.push_back({ fbo, w, h });
448
postShaderFramebuffers_.push_back(fbo);
449
return true;
450
}
451
452
void PresentationCommon::ShowPostShaderError(const std::string &errorString) {
453
// let's show the first line of the error string as an OSM.
454
std::set<std::string> blacklistedLines;
455
// These aren't useful to show, skip to the first interesting line.
456
blacklistedLines.insert("Fragment shader failed to compile with the following errors:");
457
blacklistedLines.insert("Vertex shader failed to compile with the following errors:");
458
blacklistedLines.insert("Compile failed.");
459
blacklistedLines.insert("");
460
461
std::string firstLine;
462
size_t start = 0;
463
for (size_t i = 0; i < errorString.size(); i++) {
464
if (errorString[i] == '\n' && i == start) {
465
start = i + 1;
466
} else if (errorString[i] == '\n') {
467
firstLine = errorString.substr(start, i - start);
468
if (blacklistedLines.find(firstLine) == blacklistedLines.end()) {
469
break;
470
}
471
start = i + 1;
472
firstLine.clear();
473
}
474
}
475
if (!firstLine.empty()) {
476
g_OSD.Show(OSDType::MESSAGE_ERROR_DUMP, "Post-shader error: " + firstLine + "...:\n" + errorString, 10.0f);
477
} else {
478
g_OSD.Show(OSDType::MESSAGE_ERROR, "Post-shader error, see log for details", 10.0f);
479
}
480
}
481
482
void PresentationCommon::DeviceLost() {
483
DestroyDeviceObjects();
484
draw_ = nullptr;
485
}
486
487
void PresentationCommon::DeviceRestore(Draw::DrawContext *draw) {
488
draw_ = draw;
489
CreateDeviceObjects();
490
}
491
492
Draw::Pipeline *PresentationCommon::CreatePipeline(std::vector<Draw::ShaderModule *> shaders, bool postShader, const UniformBufferDesc *uniformDesc) const {
493
using namespace Draw;
494
495
Semantic pos = SEM_POSITION;
496
Semantic tc = SEM_TEXCOORD0;
497
// Shader translation marks these both as "TEXCOORDs" on HLSL...
498
if (postShader && lang_ == HLSL_D3D11) {
499
pos = SEM_TEXCOORD0;
500
tc = SEM_TEXCOORD1;
501
}
502
503
// TODO: Maybe get rid of color0.
504
InputLayoutDesc inputDesc = {
505
sizeof(Vertex),
506
{
507
{ pos, DataFormat::R32G32B32_FLOAT, 0 },
508
{ tc, DataFormat::R32G32_FLOAT, 12 },
509
{ SEM_COLOR0, DataFormat::R8G8B8A8_UNORM, 20 },
510
},
511
};
512
513
InputLayout *inputLayout = draw_->CreateInputLayout(inputDesc);
514
DepthStencilState *depth = draw_->CreateDepthStencilState({ false, false, Comparison::LESS });
515
BlendState *blendstateOff = draw_->CreateBlendState({ false, 0xF });
516
RasterState *rasterNoCull = draw_->CreateRasterState({});
517
518
PipelineDesc pipelineDesc{ Primitive::TRIANGLE_STRIP, shaders, inputLayout, depth, blendstateOff, rasterNoCull, uniformDesc };
519
Pipeline *pipeline = draw_->CreateGraphicsPipeline(pipelineDesc, "presentation");
520
521
inputLayout->Release();
522
depth->Release();
523
blendstateOff->Release();
524
rasterNoCull->Release();
525
526
return pipeline;
527
}
528
529
void PresentationCommon::CreateDeviceObjects() {
530
using namespace Draw;
531
_dbg_assert_(vdata_ == nullptr);
532
533
// TODO: Could probably just switch to DrawUP, it's supported well by all backends now.
534
vdata_ = draw_->CreateBuffer(sizeof(Vertex) * 12, BufferUsageFlag::DYNAMIC | BufferUsageFlag::VERTEXDATA);
535
536
samplerNearest_ = draw_->CreateSamplerState({ TextureFilter::NEAREST, TextureFilter::NEAREST, TextureFilter::NEAREST, 0.0f, TextureAddressMode::CLAMP_TO_EDGE, TextureAddressMode::CLAMP_TO_EDGE, TextureAddressMode::CLAMP_TO_EDGE });
537
samplerLinear_ = draw_->CreateSamplerState({ TextureFilter::LINEAR, TextureFilter::LINEAR, TextureFilter::LINEAR, 0.0f, TextureAddressMode::CLAMP_TO_EDGE, TextureAddressMode::CLAMP_TO_EDGE, TextureAddressMode::CLAMP_TO_EDGE });
538
539
texColor_ = CreatePipeline({ draw_->GetVshaderPreset(VS_TEXTURE_COLOR_2D), draw_->GetFshaderPreset(FS_TEXTURE_COLOR_2D) }, false, &vsTexColBufDesc);
540
texColorRBSwizzle_ = CreatePipeline({ draw_->GetVshaderPreset(VS_TEXTURE_COLOR_2D), draw_->GetFshaderPreset(FS_TEXTURE_COLOR_2D_RB_SWIZZLE) }, false, &vsTexColBufDesc);
541
542
if (restorePostShader_)
543
UpdatePostShader();
544
restorePostShader_ = false;
545
}
546
547
template <typename T>
548
static void DoRelease(T *&obj) {
549
if (obj)
550
obj->Release();
551
obj = nullptr;
552
}
553
554
template <typename T>
555
static void DoReleaseVector(std::vector<T *> &list) {
556
for (auto &obj : list)
557
obj->Release();
558
list.clear();
559
}
560
561
void PresentationCommon::DestroyDeviceObjects() {
562
DoRelease(texColor_);
563
DoRelease(texColorRBSwizzle_);
564
DoRelease(samplerNearest_);
565
DoRelease(samplerLinear_);
566
DoRelease(vdata_);
567
DoRelease(srcTexture_);
568
DoRelease(srcFramebuffer_);
569
570
restorePostShader_ = usePostShader_;
571
DestroyPostShader();
572
DestroyStereoShader();
573
}
574
575
void PresentationCommon::DestroyPostShader() {
576
usePostShader_ = false;
577
578
DoReleaseVector(postShaderPipelines_);
579
DoReleaseVector(postShaderFramebuffers_);
580
DoReleaseVector(previousFramebuffers_);
581
postShaderInfo_.clear();
582
postShaderFBOUsage_.clear();
583
}
584
585
void PresentationCommon::DestroyStereoShader() {
586
DoRelease(stereoPipeline_);
587
delete stereoShaderInfo_;
588
stereoShaderInfo_ = nullptr;
589
}
590
591
Draw::ShaderModule *PresentationCommon::CompileShaderModule(ShaderStage stage, ShaderLanguage lang, const std::string &src, std::string *errorString) const {
592
std::string translated = src;
593
if (lang != lang_) {
594
// Gonna have to upconvert the shader.
595
if (!TranslateShader(&translated, lang_, draw_->GetShaderLanguageDesc(), nullptr, src, lang, stage, errorString)) {
596
ERROR_LOG(Log::FrameBuf, "Failed to translate post-shader. Error string: '%s'\nSource code:\n%s\n", errorString->c_str(), src.c_str());
597
return nullptr;
598
}
599
}
600
Draw::ShaderModule *shader = draw_->CreateShaderModule(stage, lang_, (const uint8_t *)translated.c_str(), translated.size(), "postshader");
601
return shader;
602
}
603
604
void PresentationCommon::SourceTexture(Draw::Texture *texture, int bufferWidth, int bufferHeight) {
605
// AddRef before release and assign in case it's the same.
606
texture->AddRef();
607
608
DoRelease(srcTexture_);
609
DoRelease(srcFramebuffer_);
610
611
srcTexture_ = texture;
612
srcWidth_ = bufferWidth;
613
srcHeight_ = bufferHeight;
614
}
615
616
void PresentationCommon::SourceFramebuffer(Draw::Framebuffer *fb, int bufferWidth, int bufferHeight) {
617
fb->AddRef();
618
619
DoRelease(srcTexture_);
620
DoRelease(srcFramebuffer_);
621
622
srcFramebuffer_ = fb;
623
srcWidth_ = bufferWidth;
624
srcHeight_ = bufferHeight;
625
}
626
627
// Return value is if stereo binding succeeded.
628
bool PresentationCommon::BindSource(int binding, bool bindStereo) {
629
if (srcTexture_) {
630
draw_->BindTexture(binding, srcTexture_);
631
return false;
632
} else if (srcFramebuffer_) {
633
if (bindStereo) {
634
if (srcFramebuffer_->Layers() > 1) {
635
draw_->BindFramebufferAsTexture(srcFramebuffer_, binding, Draw::Aspect::COLOR_BIT, Draw::ALL_LAYERS);
636
return true;
637
} else {
638
// Single layer. This might be from a post shader and those don't yet support stereo.
639
draw_->BindFramebufferAsTexture(srcFramebuffer_, binding, Draw::Aspect::COLOR_BIT, 0);
640
return false;
641
}
642
} else {
643
draw_->BindFramebufferAsTexture(srcFramebuffer_, binding, Draw::Aspect::COLOR_BIT, 0);
644
return false;
645
}
646
} else {
647
_assert_(false);
648
return false;
649
}
650
}
651
652
void PresentationCommon::UpdateUniforms(bool hasVideo) {
653
hasVideo_ = hasVideo;
654
}
655
656
void PresentationCommon::CopyToOutput(OutputFlags flags, int uvRotation, float u0, float v0, float u1, float v1) {
657
draw_->Invalidate(InvalidationFlags::CACHED_RENDER_STATE);
658
659
// TODO: If shader objects have been created by now, we might have received errors.
660
// GLES can have the shader fail later, shader->failed / shader->error.
661
// This should auto-disable usePostShader_ and call ShowPostShaderError().
662
663
bool useNearest = flags & OutputFlags::NEAREST;
664
bool useStereo = gstate_c.Use(GPU_USE_SIMPLE_STEREO_PERSPECTIVE) && stereoPipeline_ != nullptr; // TODO: Also check that the backend has support for it.
665
666
const bool usePostShader = usePostShader_ && !useStereo && !(flags & OutputFlags::RB_SWIZZLE);
667
const bool isFinalAtOutputResolution = usePostShader && postShaderFramebuffers_.size() < postShaderPipelines_.size();
668
Draw::Framebuffer *postShaderOutput = nullptr;
669
int lastWidth = srcWidth_;
670
int lastHeight = srcHeight_;
671
672
int pixelWidth = pixelWidth_;
673
int pixelHeight = pixelHeight_;
674
675
// These are the output coordinates.
676
FRect frame = GetScreenFrame((float)pixelWidth, (float)pixelHeight);
677
// Note: In cardboard mode, we halve the width here to compensate
678
// for splitting the window in half, while still reusing normal centering.
679
if (g_Config.bEnableCardboardVR) {
680
frame.w /= 2.0;
681
pixelWidth /= 2;
682
}
683
FRect rc;
684
CalculateDisplayOutputRect(&rc, 480.0f, 272.0f, frame, uvRotation);
685
686
// To make buffer updates easier, we use one array of verts.
687
int postVertsOffset = (int)sizeof(Vertex) * 4;
688
689
float finalU0 = u0, finalU1 = u1, finalV0 = v0, finalV1 = v1;
690
691
if (usePostShader && !(isFinalAtOutputResolution && postShaderPipelines_.size() == 1)) {
692
// The final blit will thus use the full texture.
693
finalU0 = 0.0f;
694
finalV0 = 0.0f;
695
finalU1 = 1.0f;
696
finalV1 = 1.0f;
697
}
698
699
// Our vertex buffer is split into three parts, with four vertices each:
700
// 0-3: The final blit vertices (needs to handle cropping the input ONLY if post-processing is not enabled)
701
// 4-7: Post-processing, other passes
702
// 8-11: Post-processing, first pass (needs to handle cropping the input image, if wrong dimensions)
703
Vertex verts[12] = {
704
{ rc.x, rc.y, 0, finalU0, finalV0, 0xFFFFFFFF }, // TL
705
{ rc.x + rc.w, rc.y, 0, finalU1, finalV0, 0xFFFFFFFF }, // TR
706
{ rc.x, rc.y + rc.h, 0, finalU0, finalV1, 0xFFFFFFFF }, // BL
707
{ rc.x + rc.w, rc.y + rc.h, 0, finalU1, finalV1, 0xFFFFFFFF }, // BR
708
};
709
710
// Rescale X, Y to normalized coordinate system.
711
float invDestW = 2.0f / pixelWidth;
712
float invDestH = 2.0f / pixelHeight;
713
for (int i = 0; i < 4; i++) {
714
verts[i].x = verts[i].x * invDestW - 1.0f;
715
verts[i].y = verts[i].y * invDestH - 1.0f;
716
}
717
718
if (uvRotation != ROTATION_LOCKED_HORIZONTAL) {
719
struct {
720
float u;
721
float v;
722
} temp[4];
723
int rotation = 0;
724
// Vertical and Vertical180 needed swapping after we changed the coordinate system.
725
switch (uvRotation) {
726
case ROTATION_LOCKED_HORIZONTAL180: rotation = 2; break;
727
case ROTATION_LOCKED_VERTICAL: rotation = 3; break;
728
case ROTATION_LOCKED_VERTICAL180: rotation = 1; break;
729
}
730
731
// If we flipped, we rotate the other way.
732
if ((flags & OutputFlags::BACKBUFFER_FLIPPED) || (flags & OutputFlags::POSITION_FLIPPED)) {
733
if ((rotation & 1) != 0)
734
rotation ^= 2;
735
}
736
737
static int rotLookup[4] = { 0, 1, 3, 2 };
738
739
for (int i = 0; i < 4; i++) {
740
int otherI = rotLookup[(rotLookup[i] + rotation) & 3];
741
temp[i].u = verts[otherI].u;
742
temp[i].v = verts[otherI].v;
743
}
744
for (int i = 0; i < 4; i++) {
745
verts[i].u = temp[i].u;
746
verts[i].v = temp[i].v;
747
}
748
}
749
750
if (isFinalAtOutputResolution || useStereo) {
751
// In this mode, we ignore the g_display_rot_matrix. Apply manually.
752
if (g_display.rotation != DisplayRotation::ROTATE_0) {
753
for (int i = 0; i < 4; i++) {
754
Lin::Vec3 v(verts[i].x, verts[i].y, verts[i].z);
755
// Backwards notation, should fix that...
756
v = v * g_display.rot_matrix;
757
verts[i].x = v.x;
758
verts[i].y = v.y;
759
}
760
}
761
}
762
763
if (flags & OutputFlags::PILLARBOX) {
764
for (int i = 0; i < 4; i++) {
765
// Looks about right.
766
verts[i].x *= 0.75f;
767
}
768
}
769
770
// Finally, we compensate the y vertex positions for the backbuffer for any flipping.
771
if ((flags & OutputFlags::POSITION_FLIPPED) || (flags & OutputFlags::BACKBUFFER_FLIPPED)) {
772
for (int i = 0; i < 4; i++) {
773
verts[i].y = -verts[i].y;
774
}
775
}
776
777
// Grab the previous framebuffer early so we can change previousIndex_ when we want.
778
Draw::Framebuffer *previousFramebuffer = previousFramebuffers_.empty() ? nullptr : previousFramebuffers_[previousIndex_];
779
780
PostShaderUniforms uniforms;
781
const auto performShaderPass = [&](const ShaderInfo *shaderInfo, Draw::Framebuffer *postShaderFramebuffer, Draw::Pipeline *postShaderPipeline, int vertsOffset) {
782
if (postShaderOutput) {
783
draw_->BindFramebufferAsTexture(postShaderOutput, 0, Draw::Aspect::COLOR_BIT, 0);
784
} else {
785
BindSource(0, false);
786
}
787
BindSource(1, false);
788
if (shaderInfo->usePreviousFrame)
789
draw_->BindFramebufferAsTexture(previousFramebuffer, 2, Draw::Aspect::COLOR_BIT, 0);
790
791
int nextWidth, nextHeight;
792
draw_->GetFramebufferDimensions(postShaderFramebuffer, &nextWidth, &nextHeight);
793
Draw::Viewport viewport{ 0, 0, (float)nextWidth, (float)nextHeight, 0.0f, 1.0f };
794
draw_->SetViewport(viewport);
795
draw_->SetScissorRect(0, 0, nextWidth, nextHeight);
796
797
CalculatePostShaderUniforms(lastWidth, lastHeight, nextWidth, nextHeight, shaderInfo, &uniforms);
798
799
draw_->BindPipeline(postShaderPipeline);
800
draw_->UpdateDynamicUniformBuffer(&uniforms, sizeof(uniforms));
801
802
Draw::SamplerState *sampler = useNearest || shaderInfo->isUpscalingFilter ? samplerNearest_ : samplerLinear_;
803
draw_->BindSamplerStates(0, 1, &sampler);
804
draw_->BindSamplerStates(1, 1, &sampler);
805
if (shaderInfo->usePreviousFrame)
806
draw_->BindSamplerStates(2, 1, &sampler);
807
808
draw_->BindVertexBuffer(vdata_, vertsOffset);
809
draw_->Draw(4, 0);
810
811
postShaderOutput = postShaderFramebuffer;
812
lastWidth = nextWidth;
813
lastHeight = nextHeight;
814
};
815
816
if (usePostShader) {
817
// When we render to temp framebuffers during post, we switch position, not UV.
818
// The flipping here is only because D3D has a clip coordinate system that doesn't match their screen coordinate system.
819
// The flipping here is only because D3D has a clip coordinate system that doesn't match their screen coordinate system.
820
bool flipped = flags & OutputFlags::POSITION_FLIPPED;
821
float y0 = flipped ? 1.0f : -1.0f;
822
float y1 = flipped ? -1.0f : 1.0f;
823
verts[4] = { -1.0f, y0, 0.0f, 0.0f, 0.0f, 0xFFFFFFFF }; // TL
824
verts[5] = { 1.0f, y0, 0.0f, 1.0f, 0.0f, 0xFFFFFFFF }; // TR
825
verts[6] = { -1.0f, y1, 0.0f, 0.0f, 1.0f, 0xFFFFFFFF }; // BL
826
verts[7] = { 1.0f, y1, 0.0f, 1.0f, 1.0f, 0xFFFFFFFF }; // BR
827
828
// Now, adjust for the desired input rectangle.
829
verts[8] = { -1.0f, y0, 0.0f, u0, v0, 0xFFFFFFFF }; // TL
830
verts[9] = { 1.0f, y0, 0.0f, u1, v0, 0xFFFFFFFF }; // TR
831
verts[10] = { -1.0f, y1, 0.0f, u0, v1, 0xFFFFFFFF }; // BL
832
verts[11] = { 1.0f, y1, 0.0f, u1, v1, 0xFFFFFFFF }; // BR
833
834
draw_->UpdateBuffer(vdata_, (const uint8_t *)verts, 0, sizeof(verts), Draw::UPDATE_DISCARD);
835
836
for (size_t i = 0; i < postShaderFramebuffers_.size(); ++i) {
837
Draw::Pipeline *postShaderPipeline = postShaderPipelines_[i];
838
const ShaderInfo *shaderInfo = &postShaderInfo_[i];
839
Draw::Framebuffer *postShaderFramebuffer = postShaderFramebuffers_[i];
840
if (!isFinalAtOutputResolution && i == postShaderFramebuffers_.size() - 1 && !previousFramebuffers_.empty()) {
841
// This is the last pass and we're going direct to the backbuffer after this.
842
// Redirect output to a separate framebuffer to keep the previous frame.
843
previousIndex_++;
844
if (previousIndex_ >= (int)previousFramebuffers_.size())
845
previousIndex_ = 0;
846
postShaderFramebuffer = previousFramebuffers_[previousIndex_];
847
}
848
849
draw_->BindFramebufferAsRenderTarget(postShaderFramebuffer, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "PostShader");
850
851
// Pick vertices 8-11 for the first pass.
852
int vertOffset = i == 0 ? (int)sizeof(Vertex) * 8 : (int)sizeof(Vertex) * 4;
853
performShaderPass(shaderInfo, postShaderFramebuffer, postShaderPipeline, vertOffset);
854
}
855
856
if (isFinalAtOutputResolution && postShaderInfo_.back().isUpscalingFilter)
857
useNearest = true;
858
} else {
859
// Only need to update the first four verts, the rest are unused.
860
draw_->UpdateBuffer(vdata_, (const uint8_t *)verts, 0, postVertsOffset, Draw::UPDATE_DISCARD);
861
}
862
863
// If we need to save the previous frame, we have to save any final pass in a framebuffer.
864
if (isFinalAtOutputResolution && !previousFramebuffers_.empty()) {
865
Draw::Pipeline *postShaderPipeline = postShaderPipelines_.back();
866
const ShaderInfo *shaderInfo = &postShaderInfo_.back();
867
868
// Pick the next to render to.
869
previousIndex_++;
870
if (previousIndex_ >= (int)previousFramebuffers_.size())
871
previousIndex_ = 0;
872
Draw::Framebuffer *postShaderFramebuffer = previousFramebuffers_[previousIndex_];
873
874
draw_->BindFramebufferAsRenderTarget(postShaderFramebuffer, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "InterFrameBlit");
875
performShaderPass(shaderInfo, postShaderFramebuffer, postShaderPipeline, postVertsOffset);
876
}
877
878
draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "FinalBlit");
879
draw_->SetScissorRect(0, 0, pixelWidth_, pixelHeight_);
880
881
Draw::Pipeline *pipeline = (flags & OutputFlags::RB_SWIZZLE) ? texColorRBSwizzle_ : texColor_;
882
883
if (useStereo) {
884
draw_->BindPipeline(stereoPipeline_);
885
if (!BindSource(0, true)) {
886
// Fall back
887
draw_->BindPipeline(texColor_);
888
useStereo = false; // Otherwise we end up uploading the wrong uniforms
889
}
890
} else {
891
if (isFinalAtOutputResolution && previousFramebuffers_.empty()) {
892
pipeline = postShaderPipelines_.back();
893
}
894
895
draw_->BindPipeline(pipeline);
896
if (postShaderOutput) {
897
draw_->BindFramebufferAsTexture(postShaderOutput, 0, Draw::Aspect::COLOR_BIT, 0);
898
} else {
899
BindSource(0, false);
900
}
901
}
902
BindSource(1, false);
903
904
if (isFinalAtOutputResolution && previousFramebuffers_.empty()) {
905
CalculatePostShaderUniforms(lastWidth, lastHeight, (int)rc.w, (int)rc.h, &postShaderInfo_.back(), &uniforms);
906
draw_->UpdateDynamicUniformBuffer(&uniforms, sizeof(uniforms));
907
} else if (useStereo) {
908
CalculatePostShaderUniforms(lastWidth, lastHeight, (int)rc.w, (int)rc.h, stereoShaderInfo_, &uniforms);
909
draw_->UpdateDynamicUniformBuffer(&uniforms, sizeof(uniforms));
910
} else {
911
Draw::VsTexColUB ub{};
912
memcpy(ub.WorldViewProj, g_display.rot_matrix.m, sizeof(float) * 16);
913
draw_->UpdateDynamicUniformBuffer(&ub, sizeof(ub));
914
}
915
916
draw_->BindVertexBuffer(vdata_, 0);
917
918
Draw::SamplerState *sampler = useNearest ? samplerNearest_ : samplerLinear_;
919
draw_->BindSamplerStates(0, 1, &sampler);
920
draw_->BindSamplerStates(1, 1, &sampler);
921
922
auto setViewport = [&](float x, float y, float w, float h) {
923
Draw::Viewport viewport{ x, y, w, h, 0.0f, 1.0f };
924
draw_->SetViewport(viewport);
925
};
926
927
CardboardSettings cardboardSettings;
928
GetCardboardSettings(&cardboardSettings);
929
if (cardboardSettings.enabled) {
930
// TODO: This could actually support stereo now, with an appropriate shader.
931
932
// This is what the left eye sees.
933
setViewport(cardboardSettings.leftEyeXPosition, cardboardSettings.screenYPosition, cardboardSettings.screenWidth, cardboardSettings.screenHeight);
934
draw_->Draw(4, 0);
935
936
// And this is the right eye, unless they're a pirate.
937
setViewport(cardboardSettings.rightEyeXPosition, cardboardSettings.screenYPosition, cardboardSettings.screenWidth, cardboardSettings.screenHeight);
938
draw_->Draw(4, 0);
939
} else {
940
setViewport(0.0f, 0.0f, (float)pixelWidth_, (float)pixelHeight_);
941
draw_->Draw(4, 0);
942
}
943
944
DoRelease(srcFramebuffer_);
945
DoRelease(srcTexture_);
946
947
// Unbinds all textures and samplers too, needed since sometimes a MakePixelTexture is deleted etc.
948
draw_->Invalidate(InvalidationFlags::CACHED_RENDER_STATE);
949
950
previousUniforms_ = uniforms;
951
presentedThisFrame_ = true;
952
}
953
954
void PresentationCommon::CalculateRenderResolution(int *width, int *height, int *scaleFactor, bool *upscaling, bool *ssaa) const {
955
// Check if postprocessing shader is doing upscaling as it requires native resolution
956
std::vector<const ShaderInfo *> shaderInfo;
957
if (!g_Config.vPostShaderNames.empty()) {
958
ReloadAllPostShaderInfo(draw_);
959
RemoveUnknownPostShaders(&g_Config.vPostShaderNames);
960
FixPostShaderOrder(&g_Config.vPostShaderNames);
961
shaderInfo = GetFullPostShadersChain(g_Config.vPostShaderNames);
962
}
963
964
bool firstIsUpscalingFilter = shaderInfo.empty() ? false : shaderInfo.front()->isUpscalingFilter;
965
int firstSSAAFilterLevel = shaderInfo.empty() ? 0 : shaderInfo.front()->SSAAFilterLevel;
966
967
// In auto mode (zoom == 0), round up to an integer zoom factor for the render size.
968
int zoom = g_Config.iInternalResolution;
969
if (zoom == 0 || firstSSAAFilterLevel >= 2) {
970
// auto mode, use the longest dimension
971
if (!g_Config.IsPortrait()) {
972
zoom = (PSP_CoreParameter().pixelWidth + 479) / 480;
973
} else {
974
zoom = (PSP_CoreParameter().pixelHeight + 479) / 480;
975
}
976
if (firstSSAAFilterLevel >= 2)
977
zoom *= firstSSAAFilterLevel;
978
}
979
if (zoom <= 1 || firstIsUpscalingFilter)
980
zoom = 1;
981
982
if (upscaling) {
983
*upscaling = firstIsUpscalingFilter;
984
for (auto &info : shaderInfo) {
985
*upscaling = *upscaling || info->isUpscalingFilter;
986
}
987
}
988
if (ssaa) {
989
*ssaa = firstSSAAFilterLevel >= 2;
990
for (auto &info : shaderInfo) {
991
*ssaa = *ssaa || info->SSAAFilterLevel >= 2;
992
}
993
}
994
995
if (IsVREnabled()) {
996
*width = 480 * zoom;
997
*height = 480 * zoom;
998
} else {
999
// Note: We previously checked g_Config.IsPortrait (internal rotation) here but that was wrong -
1000
// we still render at 480x272 * zoom.
1001
*width = 480 * zoom;
1002
*height = 272 * zoom;
1003
}
1004
1005
*scaleFactor = zoom;
1006
}
1007
1008