2014-09-09 15:12:42 +00:00
// Copyright (c) 2012- PPSSPP Project.
2020-05-24 18:27:58 +00:00
// This program is free software: you can redistribute it and/or modify
2014-09-09 15:12:42 +00:00
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
# include <algorithm>
2015-09-23 10:25:38 +00:00
# include <sstream>
2017-11-05 09:11:00 +00:00
# include <cmath>
2015-09-23 10:25:38 +00:00
2017-02-06 11:02:30 +00:00
# include "ext/native/thin3d/thin3d.h"
2017-02-06 23:29:02 +00:00
# include "gfx_es2/gpu_features.h"
2017-02-06 11:02:30 +00:00
2020-10-01 11:05:04 +00:00
# include "Common/Data/Text/I18n.h"
2017-04-09 22:10:07 +00:00
# include "Common/ColorConv.h"
2014-09-09 15:12:42 +00:00
# include "Common/Common.h"
2014-09-10 05:56:54 +00:00
# include "Core/Config.h"
2018-06-17 01:42:31 +00:00
# include "Core/ConfigValues.h"
2020-07-04 18:57:05 +00:00
# include "Core/Core.h"
2014-09-10 05:56:54 +00:00
# include "Core/CoreParameter.h"
2016-05-28 05:00:14 +00:00
# include "Core/Host.h"
2014-09-10 05:56:54 +00:00
# include "Core/Reporting.h"
2017-10-18 10:26:02 +00:00
# include "GPU/Common/DrawEngineCommon.h"
2020-08-03 21:17:22 +00:00
# include "GPU/Common/FramebufferManagerCommon.h"
2017-04-24 18:58:16 +00:00
# include "GPU/Common/PostShader.h"
2020-05-10 05:06:22 +00:00
# include "GPU/Common/PresentationCommon.h"
2017-02-06 11:02:30 +00:00
# include "GPU/Common/TextureCacheCommon.h"
2018-11-17 17:21:51 +00:00
# include "GPU/Debugger/Record.h"
2014-09-10 05:56:54 +00:00
# include "GPU/GPUInterface.h"
2014-09-09 15:12:42 +00:00
# include "GPU/GPUState.h"
2017-06-02 15:03:29 +00:00
FramebufferManagerCommon : : FramebufferManagerCommon ( Draw : : DrawContext * draw )
: draw_ ( draw ) ,
displayFormat_ ( GE_FORMAT_565 ) {
2020-05-10 05:06:22 +00:00
presentation_ = new PresentationCommon ( draw ) ;
2014-09-09 15:12:42 +00:00
}
FramebufferManagerCommon : : ~ FramebufferManagerCommon ( ) {
2017-11-01 20:42:19 +00:00
DecimateFBOs ( ) ;
for ( auto vfb : vfbs_ ) {
DestroyFramebuf ( vfb ) ;
}
vfbs_ . clear ( ) ;
for ( auto & tempFB : tempFBOs_ ) {
tempFB . second . fbo - > Release ( ) ;
}
tempFBOs_ . clear ( ) ;
// Do the same for ReadFramebuffersToMemory's VFBs
for ( auto vfb : bvfbs_ ) {
DestroyFramebuf ( vfb ) ;
}
bvfbs_ . clear ( ) ;
2017-11-29 17:53:52 +00:00
2020-05-10 05:06:22 +00:00
delete presentation_ ;
2014-09-09 15:12:42 +00:00
}
2014-09-13 23:47:23 +00:00
void FramebufferManagerCommon : : Init ( ) {
2020-05-16 16:57:01 +00:00
// We may need to override the render size if the shader is upscaling or SSAA.
Resized ( ) ;
2014-09-13 23:47:23 +00:00
}
2017-04-14 06:35:07 +00:00
bool FramebufferManagerCommon : : UpdateSize ( ) {
const bool newRender = renderWidth_ ! = ( float ) PSP_CoreParameter ( ) . renderWidth | | renderHeight_ ! = ( float ) PSP_CoreParameter ( ) . renderHeight ;
2018-12-14 13:01:08 +00:00
const bool newSettings = bloomHack_ ! = g_Config . iBloomHack | | useBufferedRendering_ ! = ( g_Config . iRenderingMode ! = FB_NON_BUFFERED_MODE ) ;
2017-04-14 06:35:07 +00:00
2015-09-27 17:59:47 +00:00
renderWidth_ = ( float ) PSP_CoreParameter ( ) . renderWidth ;
renderHeight_ = ( float ) PSP_CoreParameter ( ) . renderHeight ;
pixelWidth_ = PSP_CoreParameter ( ) . pixelWidth ;
pixelHeight_ = PSP_CoreParameter ( ) . pixelHeight ;
2017-04-14 06:35:07 +00:00
bloomHack_ = g_Config . iBloomHack ;
2017-04-24 08:05:58 +00:00
useBufferedRendering_ = g_Config . iRenderingMode ! = FB_NON_BUFFERED_MODE ;
2017-04-14 06:35:07 +00:00
2020-05-10 23:53:15 +00:00
presentation_ - > UpdateSize ( pixelWidth_ , pixelHeight_ , renderWidth_ , renderHeight_ ) ;
2020-05-10 05:06:22 +00:00
2017-04-14 06:35:07 +00:00
return newRender | | newSettings ;
2015-09-27 17:59:47 +00:00
}
2014-09-10 05:56:54 +00:00
void FramebufferManagerCommon : : BeginFrame ( ) {
DecimateFBOs ( ) ;
2017-10-25 18:28:12 +00:00
currentRenderVfb_ = nullptr ;
2014-09-10 05:56:54 +00:00
}
2014-09-09 15:12:42 +00:00
void FramebufferManagerCommon : : SetDisplayFramebuffer ( u32 framebuf , u32 stride , GEBufferFormat format ) {
displayFramebufPtr_ = framebuf ;
displayStride_ = stride ;
displayFormat_ = format ;
2018-11-17 17:21:51 +00:00
GPURecord : : NotifyDisplay ( framebuf , stride , format ) ;
2014-09-09 15:12:42 +00:00
}
2014-09-10 05:09:41 +00:00
VirtualFramebuffer * FramebufferManagerCommon : : GetVFBAt ( u32 addr ) {
2018-11-12 06:48:30 +00:00
addr & = 0x3FFFFFFF ;
2016-01-05 06:23:37 +00:00
VirtualFramebuffer * match = nullptr ;
2014-09-10 05:09:41 +00:00
for ( size_t i = 0 ; i < vfbs_ . size ( ) ; + + i ) {
VirtualFramebuffer * v = vfbs_ [ i ] ;
2018-11-12 06:48:30 +00:00
if ( v - > fb_address = = addr ) {
2014-09-10 05:09:41 +00:00
// Could check w too but whatever
2016-01-05 06:23:37 +00:00
if ( match = = nullptr | | match - > last_frame_render < v - > last_frame_render ) {
2014-09-10 05:09:41 +00:00
match = v ;
}
}
}
2016-01-05 06:23:37 +00:00
return match ;
2014-09-10 05:09:41 +00:00
}
2019-09-17 12:45:40 +00:00
u32 FramebufferManagerCommon : : ColorBufferByteSize ( const VirtualFramebuffer * vfb ) const {
2014-09-10 05:56:54 +00:00
return vfb - > fb_stride * vfb - > height * ( vfb - > format = = GE_FORMAT_8888 ? 4 : 2 ) ;
}
bool FramebufferManagerCommon : : ShouldDownloadFramebuffer ( const VirtualFramebuffer * vfb ) const {
2019-06-02 18:48:46 +00:00
return PSP_CoreParameter ( ) . compat . flags ( ) . Force04154000Download & & vfb - > fb_address = = 0x04154000 ;
2014-09-10 05:56:54 +00:00
}
2014-09-09 15:12:42 +00:00
// Heuristics to figure out the size of FBO to create.
2019-09-17 12:45:40 +00:00
// TODO: Possibly differentiate on whether through mode is used (since in through mode, viewport is meaningless?)
2015-08-05 00:43:40 +00:00
void FramebufferManagerCommon : : EstimateDrawingSize ( u32 fb_address , GEBufferFormat fb_format , int viewport_width , int viewport_height , int region_width , int region_height , int scissor_width , int scissor_height , int fb_stride , int & drawing_width , int & drawing_height ) {
2014-09-09 15:12:42 +00:00
static const int MAX_FRAMEBUF_HEIGHT = 512 ;
// Games don't always set any of these. Take the greatest parameter that looks valid based on stride.
2017-11-04 10:41:44 +00:00
if ( viewport_width > 4 & & viewport_width < = fb_stride & & viewport_height > 0 ) {
2014-09-09 15:12:42 +00:00
drawing_width = viewport_width ;
drawing_height = viewport_height ;
// Some games specify a viewport with 0.5, but don't have VRAM for 273. 480x272 is the buffer size.
if ( viewport_width = = 481 & & region_width = = 480 & & viewport_height = = 273 & & region_height = = 272 ) {
drawing_width = 480 ;
drawing_height = 272 ;
}
// Sometimes region is set larger than the VRAM for the framebuffer.
2015-01-06 08:02:05 +00:00
// However, in one game it's correctly set as a larger height (see #7277) with the same width.
// A bit of a hack, but we try to handle that unusual case here.
if ( region_width < = fb_stride & & ( region_width > drawing_width | | ( region_width = = drawing_width & & region_height > drawing_height ) ) & & region_height < = MAX_FRAMEBUF_HEIGHT ) {
2014-09-09 15:12:42 +00:00
drawing_width = region_width ;
drawing_height = std : : max ( drawing_height , region_height ) ;
}
// Scissor is often set to a subsection of the framebuffer, so we pay the least attention to it.
if ( scissor_width < = fb_stride & & scissor_width > drawing_width & & scissor_height < = MAX_FRAMEBUF_HEIGHT ) {
drawing_width = scissor_width ;
drawing_height = std : : max ( drawing_height , scissor_height ) ;
}
} else {
// If viewport wasn't valid, let's just take the greatest anything regardless of stride.
drawing_width = std : : min ( std : : max ( region_width , scissor_width ) , fb_stride ) ;
drawing_height = std : : max ( region_height , scissor_height ) ;
}
2020-05-21 18:37:36 +00:00
if ( scissor_width = = 481 & & region_width = = 480 & & scissor_height = = 273 & & region_height = = 272 ) {
drawing_width = 480 ;
drawing_height = 272 ;
}
2014-09-09 15:12:42 +00:00
// Assume no buffer is > 512 tall, it couldn't be textured or displayed fully if so.
if ( drawing_height > = MAX_FRAMEBUF_HEIGHT ) {
if ( region_height < MAX_FRAMEBUF_HEIGHT ) {
drawing_height = region_height ;
} else if ( scissor_height < MAX_FRAMEBUF_HEIGHT ) {
drawing_height = scissor_height ;
}
}
if ( viewport_width ! = region_width ) {
// The majority of the time, these are equal. If not, let's check what we know.
u32 nearest_address = 0xFFFFFFFF ;
for ( size_t i = 0 ; i < vfbs_ . size ( ) ; + + i ) {
2018-11-11 09:54:28 +00:00
const u32 other_address = vfbs_ [ i ] - > fb_address & 0x3FFFFFFF ;
2018-11-12 06:52:58 +00:00
if ( other_address > fb_address & & other_address < nearest_address ) {
2014-09-09 15:12:42 +00:00
nearest_address = other_address ;
}
}
// Unless the game is using overlapping buffers, the next buffer should be far enough away.
// This catches some cases where we can know this.
// Hmm. The problem is that we could only catch it for the first of two buffers...
2015-08-05 00:43:40 +00:00
const u32 bpp = fb_format = = GE_FORMAT_8888 ? 4 : 2 ;
2018-11-12 06:52:58 +00:00
int avail_height = ( nearest_address - fb_address ) / ( fb_stride * bpp ) ;
2014-09-09 15:12:42 +00:00
if ( avail_height < drawing_height & & avail_height = = region_height ) {
drawing_width = std : : min ( region_width , fb_stride ) ;
drawing_height = avail_height ;
}
// Some games draw buffers interleaved, with a high stride/region/scissor but default viewport.
if ( fb_stride = = 1024 & & region_width = = 1024 & & scissor_width = = 1024 ) {
drawing_width = 1024 ;
}
}
2015-08-05 00:43:40 +00:00
DEBUG_LOG ( G3D , " Est: %08x V: %ix%i, R: %ix%i, S: %ix%i, STR: %i, THR:%i, Z:%08x = %ix%i " , fb_address , viewport_width , viewport_height , region_width , region_height , scissor_width , scissor_height , fb_stride , gstate . isModeThrough ( ) , gstate . isDepthWriteEnabled ( ) ? gstate . getDepthBufAddress ( ) : 0 , drawing_width , drawing_height ) ;
2014-09-09 15:12:42 +00:00
}
2014-09-10 05:56:54 +00:00
2015-08-05 09:51:24 +00:00
void GetFramebufferHeuristicInputs ( FramebufferHeuristicParams * params , const GPUgstate & gstate ) {
2018-11-11 09:54:28 +00:00
params - > fb_address = ( gstate . getFrameBufRawAddress ( ) & 0x3FFFFFFF ) | 0x04000000 ; // GetFramebufferHeuristicInputs is only called from rendering, and thus, it's VRAM.
2015-08-05 09:51:24 +00:00
params - > fb_stride = gstate . FrameBufStride ( ) ;
2014-09-10 05:56:54 +00:00
2018-11-11 09:54:28 +00:00
params - > z_address = ( gstate . getDepthBufRawAddress ( ) & 0x3FFFFFFF ) | 0x04000000 ;
2015-08-05 09:51:24 +00:00
params - > z_stride = gstate . DepthBufStride ( ) ;
2014-09-10 05:56:54 +00:00
2020-09-21 07:56:52 +00:00
if ( params - > z_address = = params - > fb_address ) {
// Probably indicates that the game doesn't care about Z for this VFB.
// Let's avoid matching it for Z copies and other shenanigans.
params - > z_address = 0 ;
params - > z_stride = 0 ;
}
2015-08-05 09:51:24 +00:00
params - > fmt = gstate . FrameBufFormat ( ) ;
2015-08-05 00:43:40 +00:00
2015-08-05 09:51:24 +00:00
params - > isClearingDepth = gstate . isModeClear ( ) & & gstate . isClearModeDepthMask ( ) ;
2015-08-05 00:43:40 +00:00
// Technically, it may write depth later, but we're trying to detect it only when it's really true.
if ( gstate . isModeClear ( ) ) {
// Not quite seeing how this makes sense..
2015-08-05 09:51:24 +00:00
params - > isWritingDepth = ! gstate . isClearModeDepthMask ( ) & & gstate . isDepthWriteEnabled ( ) ;
2015-08-05 00:43:40 +00:00
} else {
2015-08-05 09:51:24 +00:00
params - > isWritingDepth = gstate . isDepthWriteEnabled ( ) ;
2015-08-05 00:43:40 +00:00
}
2015-08-05 09:51:24 +00:00
params - > isDrawing = ! gstate . isModeClear ( ) | | ! gstate . isClearModeColorMask ( ) | | ! gstate . isClearModeAlphaMask ( ) ;
params - > isModeThrough = gstate . isModeThrough ( ) ;
2014-09-10 05:56:54 +00:00
2015-08-05 00:43:40 +00:00
// Viewport-X1 and Y1 are not the upper left corner, but half the width/height. A bit confusing.
2017-11-04 10:41:44 +00:00
float vpx = gstate . getViewportXScale ( ) ;
float vpy = gstate . getViewportYScale ( ) ;
// Work around problem in F1 Grand Prix, where it draws in through mode with a bogus viewport.
// We set bad values to 0 which causes the framebuffer size heuristic to rely on the other parameters instead.
2017-11-05 12:44:23 +00:00
if ( std : : isnan ( vpx ) | | vpx > 10000000.0f ) {
2017-11-04 10:41:44 +00:00
vpx = 0.f ;
}
2017-11-05 12:44:23 +00:00
if ( std : : isnan ( vpy ) | | vpy > 10000000.0f ) {
2017-11-04 10:41:44 +00:00
vpy = 0.f ;
}
params - > viewportWidth = ( int ) ( fabsf ( vpx ) * 2.0f ) ;
params - > viewportHeight = ( int ) ( fabsf ( vpy ) * 2.0f ) ;
2015-08-05 09:51:24 +00:00
params - > regionWidth = gstate . getRegionX2 ( ) + 1 ;
params - > regionHeight = gstate . getRegionY2 ( ) + 1 ;
params - > scissorWidth = gstate . getScissorX2 ( ) + 1 ;
params - > scissorHeight = gstate . getScissorY2 ( ) + 1 ;
}
2015-08-05 10:13:14 +00:00
VirtualFramebuffer * FramebufferManagerCommon : : DoSetRenderFrameBuffer ( const FramebufferHeuristicParams & params , u32 skipDrawReason ) {
2017-01-23 22:25:09 +00:00
gstate_c . Clean ( DIRTY_FRAMEBUF ) ;
2015-08-05 09:51:24 +00:00
// Collect all parameters. This whole function has really become a cesspool of heuristics...
// but it appears that's what it takes, unless we emulate VRAM layout more accurately somehow.
2015-08-04 23:03:49 +00:00
2014-09-10 05:56:54 +00:00
// As there are no clear "framebuffer width" and "framebuffer height" registers,
// we need to infer the size of the current framebuffer somehow.
int drawing_width , drawing_height ;
2015-08-05 09:51:24 +00:00
EstimateDrawingSize ( params . fb_address , params . fmt , params . viewportWidth , params . viewportHeight , params . regionWidth , params . regionHeight , params . scissorWidth , params . scissorHeight , std : : max ( params . fb_stride , 4 ) , drawing_width , drawing_height ) ;
2014-09-10 05:56:54 +00:00
2017-04-03 21:51:54 +00:00
gstate_c . SetCurRTOffsetX ( 0 ) ;
2014-09-10 05:56:54 +00:00
bool vfbFormatChanged = false ;
// Find a matching framebuffer
2017-10-25 19:19:42 +00:00
VirtualFramebuffer * vfb = nullptr ;
2014-09-10 05:56:54 +00:00
for ( size_t i = 0 ; i < vfbs_ . size ( ) ; + + i ) {
VirtualFramebuffer * v = vfbs_ [ i ] ;
2015-08-05 09:51:24 +00:00
if ( v - > fb_address = = params . fb_address ) {
2014-09-10 05:56:54 +00:00
vfb = v ;
// Update fb stride in case it changed
2015-08-05 09:51:24 +00:00
if ( vfb - > fb_stride ! = params . fb_stride | | vfb - > format ! = params . fmt ) {
2014-09-10 05:56:54 +00:00
vfbFormatChanged = true ;
2015-08-05 09:51:24 +00:00
vfb - > fb_stride = params . fb_stride ;
vfb - > format = params . fmt ;
2014-09-10 05:56:54 +00:00
}
2018-11-11 22:13:53 +00:00
2020-09-21 07:56:52 +00:00
if ( vfb - > z_address = = 0 & & vfb - > z_stride = = 0 & & params . z_stride ! = 0 ) {
2018-11-11 22:13:53 +00:00
// Got one that was created by CreateRAMFramebuffer. Since it has no depth buffer,
// we just recreate it immediately.
ResizeFramebufFBO ( vfb , vfb - > width , vfb - > height , true ) ;
}
2016-01-18 20:57:37 +00:00
// Keep track, but this isn't really used.
vfb - > z_stride = params . z_stride ;
2015-08-05 00:43:40 +00:00
// Heuristic: In throughmode, a higher height could be used. Let's avoid shrinking the buffer.
2017-04-12 07:20:50 +00:00
if ( params . isModeThrough & & ( int ) vfb - > width < = params . fb_stride ) {
2014-09-10 05:56:54 +00:00
vfb - > width = std : : max ( ( int ) vfb - > width , drawing_width ) ;
vfb - > height = std : : max ( ( int ) vfb - > height , drawing_height ) ;
} else {
vfb - > width = drawing_width ;
vfb - > height = drawing_height ;
}
break ;
2015-08-05 09:51:24 +00:00
} else if ( v - > fb_address < params . fb_address & & v - > fb_address + v - > fb_stride * 4 > params . fb_address ) {
2014-09-10 05:56:54 +00:00
// Possibly a render-to-offset.
const u32 bpp = v - > format = = GE_FORMAT_8888 ? 4 : 2 ;
2015-08-05 09:51:24 +00:00
const int x_offset = ( params . fb_address - v - > fb_address ) / bpp ;
if ( v - > format = = params . fmt & & v - > fb_stride = = params . fb_stride & & x_offset < params . fb_stride & & v - > height > = drawing_height ) {
2014-09-10 05:56:54 +00:00
WARN_LOG_REPORT_ONCE ( renderoffset , HLE , " Rendering to framebuffer offset: %08x +%dx%d " , v - > fb_address , x_offset , 0 ) ;
vfb = v ;
2017-04-03 21:51:54 +00:00
gstate_c . SetCurRTOffsetX ( x_offset ) ;
2014-09-10 05:56:54 +00:00
vfb - > width = std : : max ( ( int ) vfb - > width , x_offset + drawing_width ) ;
// To prevent the newSize code from being confused.
drawing_width + = x_offset ;
break ;
}
}
}
if ( vfb ) {
if ( ( drawing_width ! = vfb - > bufferWidth | | drawing_height ! = vfb - > bufferHeight ) ) {
// Even if it's not newly wrong, if this is larger we need to resize up.
if ( vfb - > width > vfb - > bufferWidth | | vfb - > height > vfb - > bufferHeight ) {
ResizeFramebufFBO ( vfb , vfb - > width , vfb - > height ) ;
} else if ( vfb - > newWidth ! = drawing_width | | vfb - > newHeight ! = drawing_height ) {
// If it's newly wrong, or changing every frame, just keep track.
vfb - > newWidth = drawing_width ;
vfb - > newHeight = drawing_height ;
vfb - > lastFrameNewSize = gpuStats . numFlips ;
} else if ( vfb - > lastFrameNewSize + FBO_OLD_AGE < gpuStats . numFlips ) {
// Okay, it's changed for a while (and stayed that way.) Let's start over.
// But only if we really need to, to avoid blinking.
2015-08-05 09:51:24 +00:00
bool needsRecreate = vfb - > bufferWidth > params . fb_stride ;
2014-09-10 05:56:54 +00:00
needsRecreate = needsRecreate | | vfb - > newWidth > vfb - > bufferWidth | | vfb - > newWidth * 2 < vfb - > bufferWidth ;
2014-09-10 23:50:31 +00:00
needsRecreate = needsRecreate | | vfb - > newHeight > vfb - > bufferHeight | | vfb - > newHeight * 2 < vfb - > bufferHeight ;
2014-09-10 05:56:54 +00:00
if ( needsRecreate ) {
ResizeFramebufFBO ( vfb , vfb - > width , vfb - > height , true ) ;
2016-05-20 03:55:34 +00:00
// Let's discard this information, might be wrong now.
vfb - > safeWidth = 0 ;
vfb - > safeHeight = 0 ;
2015-09-24 06:57:59 +00:00
} else {
// Even though we won't resize it, let's at least change the size params.
vfb - > width = drawing_width ;
vfb - > height = drawing_height ;
2014-09-10 05:56:54 +00:00
}
}
} else {
// It's not different, let's keep track of that too.
vfb - > lastFrameNewSize = gpuStats . numFlips ;
}
}
2015-09-19 14:19:03 +00:00
float renderWidthFactor = renderWidth_ / 480.0f ;
float renderHeightFactor = renderHeight_ / 272.0f ;
2014-09-10 05:56:54 +00:00
2019-06-02 18:48:46 +00:00
if ( PSP_CoreParameter ( ) . compat . flags ( ) . Force04154000Download & & params . fb_address = = 0x04154000 ) {
2014-09-10 05:56:54 +00:00
renderWidthFactor = 1.0 ;
renderHeightFactor = 1.0 ;
}
// None found? Create one.
if ( ! vfb ) {
2018-11-04 23:28:01 +00:00
vfb = new VirtualFramebuffer { } ;
2016-05-20 03:55:34 +00:00
vfb - > fbo = nullptr ;
2015-08-05 09:51:24 +00:00
vfb - > fb_address = params . fb_address ;
vfb - > fb_stride = params . fb_stride ;
vfb - > z_address = params . z_address ;
vfb - > z_stride = params . z_stride ;
2014-09-10 05:56:54 +00:00
vfb - > width = drawing_width ;
vfb - > height = drawing_height ;
vfb - > newWidth = drawing_width ;
vfb - > newHeight = drawing_height ;
vfb - > lastFrameNewSize = gpuStats . numFlips ;
vfb - > renderWidth = ( u16 ) ( drawing_width * renderWidthFactor ) ;
vfb - > renderHeight = ( u16 ) ( drawing_height * renderHeightFactor ) ;
vfb - > bufferWidth = drawing_width ;
vfb - > bufferHeight = drawing_height ;
2015-08-05 09:51:24 +00:00
vfb - > format = params . fmt ;
vfb - > drawnFormat = params . fmt ;
2014-09-10 05:56:54 +00:00
vfb - > usageFlags = FB_USAGE_RENDERTARGET ;
2015-08-05 10:13:14 +00:00
SetColorUpdated ( vfb , skipDrawReason ) ;
2014-09-10 05:56:54 +00:00
2019-09-17 12:45:40 +00:00
u32 byteSize = ColorBufferByteSize ( vfb ) ;
2018-11-11 09:54:28 +00:00
if ( Memory : : IsVRAMAddress ( params . fb_address ) & & params . fb_address + byteSize > framebufRangeEnd_ ) {
framebufRangeEnd_ = params . fb_address + byteSize ;
2014-09-10 05:56:54 +00:00
}
ResizeFramebufFBO ( vfb , drawing_width , drawing_height , true ) ;
NotifyRenderFramebufferCreated ( vfb ) ;
2017-11-22 11:24:05 +00:00
INFO_LOG ( FRAMEBUF , " Creating FBO for %08x (z: %08x) : %i x %i x %i " , vfb - > fb_address , vfb - > z_address , vfb - > width , vfb - > height , vfb - > format ) ;
2014-09-10 05:56:54 +00:00
vfb - > last_frame_render = gpuStats . numFlips ;
frameLastFramebufUsed_ = gpuStats . numFlips ;
vfbs_ . push_back ( vfb ) ;
currentRenderVfb_ = vfb ;
2017-12-25 19:17:59 +00:00
if ( useBufferedRendering_ & & ! g_Config . bDisableSlowFramebufEffects ) {
2018-11-11 09:54:28 +00:00
gpu - > PerformMemoryUpload ( params . fb_address , byteSize ) ;
2020-05-19 04:30:56 +00:00
NotifyStencilUpload ( params . fb_address , byteSize , StencilUpload : : STENCIL_IS_ZERO ) ;
2014-09-11 06:58:07 +00:00
// TODO: Is it worth trying to upload the depth buffer?
}
2014-09-10 05:56:54 +00:00
// Let's check for depth buffer overlap. Might be interesting.
bool sharingReported = false ;
for ( size_t i = 0 , end = vfbs_ . size ( ) ; i < end ; + + i ) {
2015-08-05 09:51:24 +00:00
if ( vfbs_ [ i ] - > z_stride ! = 0 & & params . fb_address = = vfbs_ [ i ] - > z_address ) {
2014-09-10 05:56:54 +00:00
// If it's clearing it, most likely it just needs more video memory.
// Technically it could write something interesting and the other might not clear, but that's not likely.
2015-08-05 09:51:24 +00:00
if ( params . isDrawing ) {
if ( params . fb_address ! = params . z_address & & vfbs_ [ i ] - > fb_address ! = vfbs_ [ i ] - > z_address ) {
WARN_LOG_REPORT ( SCEGE , " FBO created from existing depthbuffer as color, %08x/%08x and %08x/%08x " , params . fb_address , params . z_address , vfbs_ [ i ] - > fb_address , vfbs_ [ i ] - > z_address ) ;
2014-09-10 05:56:54 +00:00
}
}
2015-08-05 09:51:24 +00:00
} else if ( params . z_stride ! = 0 & & params . z_address = = vfbs_ [ i ] - > fb_address ) {
2014-09-10 05:56:54 +00:00
// If it's clearing it, then it's probably just the reverse of the above case.
2015-08-05 09:51:24 +00:00
if ( params . isWritingDepth ) {
WARN_LOG_REPORT ( SCEGE , " FBO using existing buffer as depthbuffer, %08x/%08x and %08x/%08x " , params . fb_address , params . z_address , vfbs_ [ i ] - > fb_address , vfbs_ [ i ] - > z_address ) ;
2014-09-10 05:56:54 +00:00
}
2015-08-05 09:51:24 +00:00
} else if ( vfbs_ [ i ] - > z_stride ! = 0 & & params . z_address = = vfbs_ [ i ] - > z_address & & params . fb_address ! = vfbs_ [ i ] - > fb_address & & ! sharingReported ) {
2014-09-10 05:56:54 +00:00
// This happens a lot, but virtually always it's cleared.
// It's possible the other might not clear, but when every game is reported it's not useful.
2015-08-05 09:51:24 +00:00
if ( params . isWritingDepth ) {
2020-08-02 13:41:00 +00:00
WARN_LOG ( SCEGE , " FBO reusing depthbuffer, c=%08x/d=%08x and c=%08x/d=%08x " , params . fb_address , params . z_address , vfbs_ [ i ] - > fb_address , vfbs_ [ i ] - > z_address ) ;
2014-09-10 05:56:54 +00:00
sharingReported = true ;
}
}
}
// We already have it!
} else if ( vfb ! = currentRenderVfb_ ) {
// Use it as a render target.
2020-08-02 13:41:00 +00:00
DEBUG_LOG ( FRAMEBUF , " Switching render target to FBO for %08x: %d x %d x %d " , vfb - > fb_address , vfb - > width , vfb - > height , vfb - > format ) ;
2014-09-10 05:56:54 +00:00
vfb - > usageFlags | = FB_USAGE_RENDERTARGET ;
vfb - > last_frame_render = gpuStats . numFlips ;
frameLastFramebufUsed_ = gpuStats . numFlips ;
vfb - > dirtyAfterDisplay = true ;
2015-08-05 10:13:14 +00:00
if ( ( skipDrawReason & SKIPDRAW_SKIPFRAME ) = = 0 )
2014-09-10 05:56:54 +00:00
vfb - > reallyDirtyAfterDisplay = true ;
VirtualFramebuffer * prev = currentRenderVfb_ ;
currentRenderVfb_ = vfb ;
2015-08-05 09:51:24 +00:00
NotifyRenderFramebufferSwitched ( prev , vfb , params . isClearingDepth ) ;
2014-09-10 05:56:54 +00:00
} else {
vfb - > last_frame_render = gpuStats . numFlips ;
frameLastFramebufUsed_ = gpuStats . numFlips ;
vfb - > dirtyAfterDisplay = true ;
2015-08-05 10:13:14 +00:00
if ( ( skipDrawReason & SKIPDRAW_SKIPFRAME ) = = 0 )
2014-09-10 05:56:54 +00:00
vfb - > reallyDirtyAfterDisplay = true ;
NotifyRenderFramebufferUpdated ( vfb , vfbFormatChanged ) ;
}
gstate_c . curRTWidth = vfb - > width ;
gstate_c . curRTHeight = vfb - > height ;
gstate_c . curRTRenderWidth = vfb - > renderWidth ;
gstate_c . curRTRenderHeight = vfb - > renderHeight ;
2015-08-05 00:43:40 +00:00
return vfb ;
2014-09-10 05:56:54 +00:00
}
2014-09-13 21:44:18 +00:00
2017-02-06 11:02:30 +00:00
void FramebufferManagerCommon : : DestroyFramebuf ( VirtualFramebuffer * v ) {
2017-11-22 11:24:05 +00:00
// Notify the texture cache of both the color and depth buffers.
2020-09-20 19:46:40 +00:00
textureCache_ - > NotifyFramebuffer ( v , NOTIFY_FB_DESTROYED ) ;
2017-02-06 11:02:30 +00:00
if ( v - > fbo ) {
2017-02-17 18:22:41 +00:00
v - > fbo - > Release ( ) ;
2017-02-06 11:02:30 +00:00
v - > fbo = nullptr ;
}
// Wipe some pointers
if ( currentRenderVfb_ = = v )
2020-05-24 18:27:58 +00:00
currentRenderVfb_ = nullptr ;
2017-02-06 11:02:30 +00:00
if ( displayFramebuf_ = = v )
2020-05-24 18:27:58 +00:00
displayFramebuf_ = nullptr ;
2017-02-06 11:02:30 +00:00
if ( prevDisplayFramebuf_ = = v )
2020-05-24 18:27:58 +00:00
prevDisplayFramebuf_ = nullptr ;
2017-02-06 11:02:30 +00:00
if ( prevPrevDisplayFramebuf_ = = v )
2020-05-24 18:27:58 +00:00
prevPrevDisplayFramebuf_ = nullptr ;
2017-02-06 11:02:30 +00:00
delete v ;
}
2020-09-17 18:31:40 +00:00
void FramebufferManagerCommon : : BlitFramebufferDepth ( VirtualFramebuffer * src , VirtualFramebuffer * dst ) {
2020-09-20 21:37:44 +00:00
int w = std : : min ( src - > renderWidth , dst - > renderWidth ) ;
int h = std : : min ( src - > renderHeight , dst - > renderHeight ) ;
// Note: We prefer Blit ahead of Copy here, since at least on GL, Copy will always also copy stencil which we don't want. See #9740.
if ( gstate_c . Supports ( GPU_SUPPORTS_FRAMEBUFFER_BLIT ) ) {
draw_ - > BlitFramebuffer ( src - > fbo , 0 , 0 , w , h , dst - > fbo , 0 , 0 , w , h , Draw : : FB_DEPTH_BIT , Draw : : FB_BLIT_NEAREST , " BlitFramebufferDepth " ) ;
RebindFramebuffer ( " BlitFramebufferDepth " ) ;
} else if ( gstate_c . Supports ( GPU_SUPPORTS_COPY_IMAGE ) ) {
draw_ - > CopyFramebufferImage ( src - > fbo , 0 , 0 , 0 , 0 , dst - > fbo , 0 , 0 , 0 , 0 , w , h , 1 , Draw : : FB_DEPTH_BIT , " BlitFramebufferDepth " ) ;
RebindFramebuffer ( " BlitFramebufferDepth " ) ;
2020-09-17 18:31:40 +00:00
}
2020-09-20 21:37:44 +00:00
dst - > last_frame_depth_updated = gpuStats . numFlips ;
2020-09-17 18:31:40 +00:00
}
2017-02-06 11:05:14 +00:00
void FramebufferManagerCommon : : NotifyRenderFramebufferCreated ( VirtualFramebuffer * vfb ) {
if ( ! useBufferedRendering_ ) {
// Let's ignore rendering to targets that have not (yet) been displayed.
gstate_c . skipDrawReason | = SKIPDRAW_NON_DISPLAYED_FB ;
2018-08-25 17:01:43 +00:00
} else if ( currentRenderVfb_ ) {
DownloadFramebufferOnSwitch ( currentRenderVfb_ ) ;
2017-02-06 11:05:14 +00:00
}
2020-09-20 19:46:40 +00:00
textureCache_ - > NotifyFramebuffer ( vfb , NOTIFY_FB_CREATED ) ;
2017-02-06 11:05:14 +00:00
2020-05-24 18:27:58 +00:00
// Ugly...
2017-02-06 11:05:14 +00:00
if ( gstate_c . curRTWidth ! = vfb - > width | | gstate_c . curRTHeight ! = vfb - > height ) {
2018-09-22 04:55:11 +00:00
gstate_c . Dirty ( DIRTY_PROJTHROUGHMATRIX | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE ) ;
2017-02-06 11:05:14 +00:00
}
if ( gstate_c . curRTRenderWidth ! = vfb - > renderWidth | | gstate_c . curRTRenderHeight ! = vfb - > renderHeight ) {
gstate_c . Dirty ( DIRTY_PROJMATRIX ) ;
gstate_c . Dirty ( DIRTY_PROJTHROUGHMATRIX ) ;
}
}
2017-02-06 11:10:08 +00:00
void FramebufferManagerCommon : : NotifyRenderFramebufferUpdated ( VirtualFramebuffer * vfb , bool vfbFormatChanged ) {
if ( vfbFormatChanged ) {
2020-09-20 19:46:40 +00:00
textureCache_ - > NotifyFramebuffer ( vfb , NOTIFY_FB_UPDATED ) ;
2017-02-06 11:10:08 +00:00
if ( vfb - > drawnFormat ! = vfb - > format ) {
ReformatFramebufferFrom ( vfb , vfb - > drawnFormat ) ;
}
}
// ugly...
if ( gstate_c . curRTWidth ! = vfb - > width | | gstate_c . curRTHeight ! = vfb - > height ) {
2018-09-22 04:55:11 +00:00
gstate_c . Dirty ( DIRTY_PROJTHROUGHMATRIX | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE ) ;
2017-02-06 11:10:08 +00:00
}
if ( gstate_c . curRTRenderWidth ! = vfb - > renderWidth | | gstate_c . curRTRenderHeight ! = vfb - > renderHeight ) {
gstate_c . Dirty ( DIRTY_PROJMATRIX ) ;
gstate_c . Dirty ( DIRTY_PROJTHROUGHMATRIX ) ;
}
}
2017-02-06 23:29:02 +00:00
void FramebufferManagerCommon : : NotifyRenderFramebufferSwitched ( VirtualFramebuffer * prevVfb , VirtualFramebuffer * vfb , bool isClearingDepth ) {
if ( ShouldDownloadFramebuffer ( vfb ) & & ! vfb - > memoryUpdated ) {
2020-08-03 21:35:40 +00:00
ReadFramebufferToMemory ( vfb , 0 , 0 , vfb - > width , vfb - > height ) ;
2017-04-09 22:10:07 +00:00
vfb - > usageFlags = ( vfb - > usageFlags | FB_USAGE_DOWNLOAD ) & ~ FB_USAGE_DOWNLOAD_CLEAR ;
2019-06-02 18:48:46 +00:00
vfb - > firstFrameSaved = true ;
2017-02-06 23:29:02 +00:00
} else {
DownloadFramebufferOnSwitch ( prevVfb ) ;
}
textureCache_ - > ForgetLastTexture ( ) ;
2018-07-28 09:09:01 +00:00
shaderManager_ - > DirtyLastShader ( ) ;
2017-02-06 23:29:02 +00:00
2019-02-08 14:02:31 +00:00
if ( prevVfb ) {
2020-08-27 08:02:50 +00:00
// Copy depth value from the previously bound framebuffer to the current one.
2020-08-27 20:59:27 +00:00
// We check that the address is the same within BlitFramebufferDepth before actually blitting.
2020-08-27 08:02:50 +00:00
2017-12-24 19:52:15 +00:00
bool hasNewerDepth = prevVfb - > last_frame_depth_render ! = 0 & & prevVfb - > last_frame_depth_render > = vfb - > last_frame_depth_updated ;
if ( ! prevVfb - > fbo | | ! vfb - > fbo | | ! useBufferedRendering_ | | ! hasNewerDepth | | isClearingDepth ) {
2017-05-23 21:10:35 +00:00
// If depth wasn't updated, then we're at least "two degrees" away from the data.
// This is an optimization: it probably doesn't need to be copied in this case.
} else {
2020-09-20 21:37:44 +00:00
bool matchingDepthBuffer = prevVfb - > z_address = = vfb - > z_address & & prevVfb - > z_stride ! = 0 & & vfb - > z_stride ! = 0 ;
bool matchingSize = prevVfb - > width = = vfb - > width & & prevVfb - > height = = vfb - > height ;
if ( matchingDepthBuffer & & matchingSize ) {
BlitFramebufferDepth ( prevVfb , vfb ) ;
}
2017-05-23 21:10:35 +00:00
}
}
if ( vfb - > drawnFormat ! = vfb - > format ) {
// TODO: Might ultimately combine this with the resize step in DoSetRenderFrameBuffer().
ReformatFramebufferFrom ( vfb , vfb - > drawnFormat ) ;
}
2017-02-06 23:29:02 +00:00
if ( useBufferedRendering_ ) {
if ( vfb - > fbo ) {
2018-07-28 09:09:01 +00:00
shaderManager_ - > DirtyLastShader ( ) ;
2020-09-10 21:47:54 +00:00
if ( g_Config . bClearFramebuffersOnFirstUseHack ) {
2020-08-03 21:35:40 +00:00
// HACK: Some tiled mobile GPUs benefit IMMENSELY from clearing an FBO before rendering
// to it (or in Vulkan, clear during framebuffer load). This is a hack to force this
// the first time a framebuffer is bound for rendering in a frame.
//
// Quite unsafe as it might kill some feedback effects.
2017-05-16 14:00:34 +00:00
if ( vfb - > last_frame_render ! = gpuStats . numFlips ) {
2020-05-21 09:24:05 +00:00
draw_ - > BindFramebufferAsRenderTarget ( vfb - > fbo , { Draw : : RPAction : : CLEAR , Draw : : RPAction : : CLEAR , Draw : : RPAction : : CLEAR } , " FramebufferSwitch " ) ;
2017-05-16 14:00:34 +00:00
} else {
2020-05-21 09:24:05 +00:00
draw_ - > BindFramebufferAsRenderTarget ( vfb - > fbo , { Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP } , " FramebufferSwitch " ) ;
2017-05-16 14:00:34 +00:00
}
} else {
2020-05-21 09:24:05 +00:00
draw_ - > BindFramebufferAsRenderTarget ( vfb - > fbo , { Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP } , " FramebufferSwitch " ) ;
2017-05-16 14:00:34 +00:00
}
2017-02-06 23:29:02 +00:00
} else {
2017-04-24 16:30:04 +00:00
// This should only happen very briefly when toggling useBufferedRendering_.
ResizeFramebufFBO ( vfb , vfb - > width , vfb - > height , true ) ;
2017-02-06 23:29:02 +00:00
}
} else {
if ( vfb - > fbo ) {
2017-04-24 16:30:04 +00:00
// This should only happen very briefly when toggling useBufferedRendering_.
2020-09-20 19:46:40 +00:00
textureCache_ - > NotifyFramebuffer ( vfb , NOTIFY_FB_DESTROYED ) ;
2017-11-05 20:45:02 +00:00
vfb - > fbo - > Release ( ) ;
2017-02-06 23:29:02 +00:00
vfb - > fbo = nullptr ;
}
// Let's ignore rendering to targets that have not (yet) been displayed.
if ( vfb - > usageFlags & FB_USAGE_DISPLAYED_FRAMEBUFFER ) {
gstate_c . skipDrawReason & = ~ SKIPDRAW_NON_DISPLAYED_FB ;
} else {
gstate_c . skipDrawReason | = SKIPDRAW_NON_DISPLAYED_FB ;
}
}
2020-09-20 19:46:40 +00:00
textureCache_ - > NotifyFramebuffer ( vfb , NOTIFY_FB_UPDATED ) ;
2017-02-06 23:29:02 +00:00
2020-05-24 18:57:59 +00:00
// ugly... is all this needed?
2017-02-06 23:29:02 +00:00
if ( gstate_c . curRTWidth ! = vfb - > width | | gstate_c . curRTHeight ! = vfb - > height ) {
2018-09-22 04:55:11 +00:00
gstate_c . Dirty ( DIRTY_PROJTHROUGHMATRIX | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE ) ;
2017-02-06 23:29:02 +00:00
}
if ( gstate_c . curRTRenderWidth ! = vfb - > renderWidth | | gstate_c . curRTRenderHeight ! = vfb - > renderHeight ) {
gstate_c . Dirty ( DIRTY_PROJMATRIX ) ;
gstate_c . Dirty ( DIRTY_PROJTHROUGHMATRIX ) ;
}
}
2016-01-17 20:52:40 +00:00
void FramebufferManagerCommon : : NotifyVideoUpload ( u32 addr , int size , int width , GEBufferFormat fmt ) {
// Note: UpdateFromMemory() is still called later.
// This is a special case where we have extra information prior to the invalidation.
// TODO: Could possibly be an offset...
VirtualFramebuffer * vfb = GetVFBAt ( addr ) ;
if ( vfb ) {
if ( vfb - > format ! = fmt | | vfb - > drawnFormat ! = fmt ) {
DEBUG_LOG ( ME , " Changing format for %08x from %d to %d " , addr , vfb - > drawnFormat , fmt ) ;
vfb - > format = fmt ;
vfb - > drawnFormat = fmt ;
// Let's count this as a "render". This will also force us to use the correct format.
vfb - > last_frame_render = gpuStats . numFlips ;
}
2016-01-18 05:58:49 +00:00
if ( vfb - > fb_stride < width ) {
2016-01-18 06:33:05 +00:00
DEBUG_LOG ( ME , " Changing stride for %08x from %d to %d " , addr , vfb - > fb_stride , width ) ;
const int bpp = fmt = = GE_FORMAT_8888 ? 4 : 2 ;
ResizeFramebufFBO ( vfb , width , size / ( bpp * width ) ) ;
2017-03-19 17:56:34 +00:00
// Resizing may change the viewport/etc.
2018-09-22 04:55:11 +00:00
gstate_c . Dirty ( DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE ) ;
2016-01-18 06:33:05 +00:00
vfb - > fb_stride = width ;
// This might be a bit wider than necessary, but we'll redetect on next render.
2016-03-13 14:02:14 +00:00
vfb - > width = width ;
2016-01-18 05:58:49 +00:00
}
2016-01-17 20:52:40 +00:00
}
}
2014-09-13 21:44:18 +00:00
void FramebufferManagerCommon : : UpdateFromMemory ( u32 addr , int size , bool safe ) {
2018-11-11 09:54:28 +00:00
// Take off the uncached flag from the address. Not to be confused with the start of VRAM.
addr & = 0x3FFFFFFF ;
2014-09-13 21:44:18 +00:00
// TODO: Could go through all FBOs, but probably not important?
// TODO: Could also check for inner changes, but video is most important.
bool isDisplayBuf = addr = = DisplayFramebufAddr ( ) | | addr = = PrevDisplayFramebufAddr ( ) ;
if ( isDisplayBuf | | safe ) {
// TODO: Deleting the FBO is a heavy hammer solution, so let's only do it if it'd help.
if ( ! Memory : : IsValidAddress ( displayFramebufPtr_ ) )
return ;
for ( size_t i = 0 ; i < vfbs_ . size ( ) ; + + i ) {
VirtualFramebuffer * vfb = vfbs_ [ i ] ;
2018-11-12 06:48:30 +00:00
if ( vfb - > fb_address = = addr ) {
2014-09-13 21:44:18 +00:00
FlushBeforeCopy ( ) ;
if ( useBufferedRendering_ & & vfb - > fbo ) {
GEBufferFormat fmt = vfb - > format ;
if ( vfb - > last_frame_render + 1 < gpuStats . numFlips & & isDisplayBuf ) {
// If we're not rendering to it, format may be wrong. Use displayFormat_ instead.
fmt = displayFormat_ ;
}
2018-11-11 09:54:28 +00:00
DrawPixels ( vfb , 0 , 0 , Memory : : GetPointer ( addr ) , fmt , vfb - > fb_stride , vfb - > width , vfb - > height ) ;
2015-07-26 20:38:40 +00:00
SetColorUpdated ( vfb , gstate_c . skipDrawReason ) ;
2014-09-13 21:44:18 +00:00
} else {
2017-03-13 11:32:21 +00:00
INFO_LOG ( FRAMEBUF , " Invalidating FBO for %08x (%i x %i x %i) " , vfb - > fb_address , vfb - > width , vfb - > height , vfb - > format ) ;
2014-09-13 21:44:18 +00:00
DestroyFramebuf ( vfb ) ;
vfbs_ . erase ( vfbs_ . begin ( ) + i - - ) ;
}
}
}
2020-06-02 07:51:38 +00:00
RebindFramebuffer ( " RebindFramebuffer - UpdateFromMemory " ) ;
2014-09-13 21:44:18 +00:00
}
2017-03-19 17:25:30 +00:00
// TODO: Necessary?
2017-03-19 10:32:29 +00:00
gstate_c . Dirty ( DIRTY_FRAGMENTSHADER_STATE ) ;
2014-09-13 21:44:18 +00:00
}
2014-09-13 22:40:55 +00:00
2017-02-15 17:32:44 +00:00
void FramebufferManagerCommon : : DrawPixels ( VirtualFramebuffer * vfb , int dstX , int dstY , const u8 * srcPixels , GEBufferFormat srcPixelFormat , int srcStride , int width , int height ) {
textureCache_ - > ForgetLastTexture ( ) ;
shaderManager_ - > DirtyLastShader ( ) ; // On GL, important that this is BEFORE drawing
2017-03-23 03:56:26 +00:00
float u0 = 0.0f , u1 = 1.0f ;
2017-02-15 17:32:44 +00:00
float v0 = 0.0f , v1 = 1.0f ;
2017-03-23 03:56:26 +00:00
2020-05-12 02:25:33 +00:00
DrawTextureFlags flags ;
2017-02-15 17:32:44 +00:00
if ( useBufferedRendering_ & & vfb & & vfb - > fbo ) {
2020-05-12 02:25:33 +00:00
flags = DRAWTEX_LINEAR ;
2020-05-21 09:24:05 +00:00
draw_ - > BindFramebufferAsRenderTarget ( vfb - > fbo , { Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP } , " DrawPixels " ) ;
2020-05-20 07:47:11 +00:00
gstate_c . Dirty ( DIRTY_VIEWPORTSCISSOR_STATE ) ;
2017-02-15 17:32:44 +00:00
SetViewport2D ( 0 , 0 , vfb - > renderWidth , vfb - > renderHeight ) ;
2017-05-21 21:13:53 +00:00
draw_ - > SetScissorRect ( 0 , 0 , vfb - > renderWidth , vfb - > renderHeight ) ;
2017-02-15 17:32:44 +00:00
} else {
2020-05-31 18:13:35 +00:00
// We are drawing directly to the back buffer so need to flip.
// Should more of this be handled by the presentation engine?
2017-02-15 17:32:44 +00:00
if ( needBackBufferYSwap_ )
std : : swap ( v0 , v1 ) ;
2020-05-12 02:25:33 +00:00
flags = g_Config . iBufFilter = = SCALE_LINEAR ? DRAWTEX_LINEAR : DRAWTEX_NEAREST ;
2019-06-22 20:15:09 +00:00
flags = flags | DRAWTEX_TO_BACKBUFFER ;
2020-07-05 20:46:04 +00:00
FRect frame = GetScreenFrame ( pixelWidth_ , pixelHeight_ ) ;
2020-05-31 17:45:28 +00:00
FRect rc ;
2020-05-31 18:13:35 +00:00
CenterDisplayOutputRect ( & rc , 480.0f , 272.0f , frame , ROTATION_LOCKED_HORIZONTAL ) ;
2020-05-31 17:45:28 +00:00
SetViewport2D ( rc . x , rc . y , rc . w , rc . h ) ;
2017-05-21 21:13:53 +00:00
draw_ - > SetScissorRect ( 0 , 0 , pixelWidth_ , pixelHeight_ ) ;
2017-02-15 17:32:44 +00:00
}
2020-05-11 16:47:26 +00:00
Draw : : Texture * pixelsTex = MakePixelTexture ( srcPixels , srcPixelFormat , srcStride , width , height , u1 , v1 ) ;
if ( pixelsTex ) {
draw_ - > BindTextures ( 0 , 1 , & pixelsTex ) ;
Bind2DShader ( ) ;
DrawActiveTexture ( dstX , dstY , width , height , vfb - > bufferWidth , vfb - > bufferHeight , u0 , v0 , u1 , v1 , ROTATION_LOCKED_HORIZONTAL , flags ) ;
gpuStats . numUploads + + ;
pixelsTex - > Release ( ) ;
2020-08-22 08:50:39 +00:00
draw_ - > InvalidateCachedState ( ) ;
2020-05-12 02:25:33 +00:00
2020-09-20 20:48:35 +00:00
gstate_c . Dirty ( DIRTY_BLEND_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS ) ;
2020-05-11 16:47:26 +00:00
}
2017-02-15 17:32:44 +00:00
}
2017-04-07 01:49:48 +00:00
void FramebufferManagerCommon : : CopyFramebufferForColorTexture ( VirtualFramebuffer * dst , VirtualFramebuffer * src , int flags ) {
int x = 0 ;
int y = 0 ;
int w = src - > drawnWidth ;
int h = src - > drawnHeight ;
// If max is not > min, we probably could not detect it. Skip.
// See the vertex decoder, where this is updated.
if ( ( flags & BINDFBCOLOR_MAY_COPY_WITH_UV ) = = BINDFBCOLOR_MAY_COPY_WITH_UV & & gstate_c . vertBounds . maxU > gstate_c . vertBounds . minU ) {
x = std : : max ( gstate_c . vertBounds . minU , ( u16 ) 0 ) ;
y = std : : max ( gstate_c . vertBounds . minV , ( u16 ) 0 ) ;
w = std : : min ( gstate_c . vertBounds . maxU , src - > drawnWidth ) - x ;
h = std : : min ( gstate_c . vertBounds . maxV , src - > drawnHeight ) - y ;
// If we bound a framebuffer, apply the byte offset as pixels to the copy too.
if ( flags & BINDFBCOLOR_APPLY_TEX_OFFSET ) {
x + = gstate_c . curTextureXOffset ;
y + = gstate_c . curTextureYOffset ;
}
}
if ( x < src - > drawnWidth & & y < src - > drawnHeight & & w > 0 & & h > 0 ) {
BlitFramebuffer ( dst , x , y , src , x , y , w , h , 0 ) ;
}
}
2020-05-11 20:36:23 +00:00
Draw : : Texture * FramebufferManagerCommon : : MakePixelTexture ( const u8 * srcPixels , GEBufferFormat srcPixelFormat , int srcStride , int width , int height , float & u1 , float & v1 ) {
// TODO: We can just change the texture format and flip some bits around instead of this.
// Could share code with the texture cache perhaps.
auto generateTexture = [ & ] ( uint8_t * data , const uint8_t * initData , uint32_t w , uint32_t h , uint32_t d , uint32_t byteStride , uint32_t sliceByteStride ) {
for ( int y = 0 ; y < height ; y + + ) {
const u16_le * src16 = ( const u16_le * ) srcPixels + srcStride * y ;
const u32_le * src32 = ( const u32_le * ) srcPixels + srcStride * y ;
u32 * dst = ( u32 * ) ( data + byteStride * y ) ;
switch ( srcPixelFormat ) {
case GE_FORMAT_565 :
if ( preferredPixelsFormat_ = = Draw : : DataFormat : : B8G8R8A8_UNORM )
ConvertRGB565ToBGRA8888 ( dst , src16 , width ) ;
else
2020-05-14 01:17:58 +00:00
ConvertRGB565ToRGBA8888 ( dst , src16 , width ) ;
2020-05-11 20:36:23 +00:00
break ;
case GE_FORMAT_5551 :
if ( preferredPixelsFormat_ = = Draw : : DataFormat : : B8G8R8A8_UNORM )
ConvertRGBA5551ToBGRA8888 ( dst , src16 , width ) ;
else
ConvertRGBA5551ToRGBA8888 ( dst , src16 , width ) ;
break ;
case GE_FORMAT_4444 :
if ( preferredPixelsFormat_ = = Draw : : DataFormat : : B8G8R8A8_UNORM )
ConvertRGBA4444ToBGRA8888 ( dst , src16 , width ) ;
else
ConvertRGBA4444ToRGBA8888 ( dst , src16 , width ) ;
break ;
case GE_FORMAT_8888 :
if ( preferredPixelsFormat_ = = Draw : : DataFormat : : B8G8R8A8_UNORM )
ConvertRGBA8888ToBGRA8888 ( dst , src32 , width ) ;
2020-05-14 03:30:24 +00:00
// This means use original pointer as-is. May avoid or optimize a copy.
2020-05-15 01:45:06 +00:00
else if ( srcStride = = width )
2020-05-14 03:30:24 +00:00
return false ;
2020-05-15 01:45:06 +00:00
else
memcpy ( dst , src32 , width * 4 ) ;
2020-05-11 20:36:23 +00:00
break ;
case GE_FORMAT_INVALID :
2020-08-20 04:18:44 +00:00
case GE_FORMAT_DEPTH16 :
2020-07-19 15:47:02 +00:00
_dbg_assert_msg_ ( false , " Invalid pixelFormat passed to DrawPixels(). " ) ;
2020-05-11 20:36:23 +00:00
break ;
}
}
2020-05-14 03:30:24 +00:00
return true ;
2020-05-11 20:36:23 +00:00
} ;
Draw : : TextureDesc desc {
Draw : : TextureType : : LINEAR2D ,
preferredPixelsFormat_ ,
width ,
height ,
1 ,
1 ,
false ,
" DrawPixels " ,
{ ( uint8_t * ) srcPixels } ,
generateTexture ,
} ;
// TODO: On Vulkan, use a custom allocator? Important to use an allocator:
// Hot Shot Golf (#12355) does tons of these in a frame in some situations! So actually,
// we do use an allocator. In fact, I've now banned allocator-less textures.
Draw : : Texture * tex = draw_ - > CreateTexture ( desc ) ;
if ( ! tex )
ERROR_LOG ( G3D , " Failed to create drawpixels texture " ) ;
return tex ;
}
2020-05-12 02:11:43 +00:00
void FramebufferManagerCommon : : DrawFramebufferToOutput ( const u8 * srcPixels , GEBufferFormat srcPixelFormat , int srcStride ) {
2017-02-15 22:11:46 +00:00
textureCache_ - > ForgetLastTexture ( ) ;
shaderManager_ - > DirtyLastShader ( ) ;
2017-03-23 03:56:26 +00:00
float u0 = 0.0f , u1 = 480.0f / 512.0f ;
float v0 = 0.0f , v1 = 1.0f ;
2020-05-11 16:47:26 +00:00
Draw : : Texture * pixelsTex = MakePixelTexture ( srcPixels , srcPixelFormat , srcStride , 512 , 272 , u1 , v1 ) ;
if ( ! pixelsTex )
return ;
2017-02-15 22:11:46 +00:00
2017-04-24 18:57:16 +00:00
int uvRotation = useBufferedRendering_ ? g_Config . iInternalScreenRotation : ROTATION_LOCKED_HORIZONTAL ;
2020-05-12 02:11:43 +00:00
OutputFlags flags = g_Config . iBufFilter = = SCALE_LINEAR ? OutputFlags : : LINEAR : OutputFlags : : NEAREST ;
if ( needBackBufferYSwap_ ) {
flags | = OutputFlags : : BACKBUFFER_FLIPPED ;
}
2020-05-12 06:28:50 +00:00
// DrawActiveTexture reverses these, probably to match "up".
2020-05-12 02:11:43 +00:00
if ( GetGPUBackend ( ) = = GPUBackend : : DIRECT3D9 | | GetGPUBackend ( ) = = GPUBackend : : DIRECT3D11 ) {
2020-05-12 06:28:50 +00:00
flags | = OutputFlags : : POSITION_FLIPPED ;
2020-05-11 20:36:23 +00:00
}
2020-05-10 23:53:15 +00:00
2020-05-16 06:41:13 +00:00
presentation_ - > UpdateUniforms ( textureCache_ - > VideoIsPlaying ( ) ) ;
presentation_ - > SourceTexture ( pixelsTex , 512 , 272 ) ;
presentation_ - > CopyToOutput ( flags , uvRotation , u0 , v0 , u1 , v1 ) ;
2020-05-11 16:47:26 +00:00
pixelsTex - > Release ( ) ;
2020-05-10 23:53:15 +00:00
// PresentationCommon sets all kinds of state, we can't rely on anything.
gstate_c . Dirty ( DIRTY_ALL ) ;
2017-02-15 22:11:46 +00:00
}
2016-06-05 02:36:30 +00:00
void FramebufferManagerCommon : : DownloadFramebufferOnSwitch ( VirtualFramebuffer * vfb ) {
2017-04-09 22:19:06 +00:00
if ( vfb & & vfb - > safeWidth > 0 & & vfb - > safeHeight > 0 & & ! vfb - > firstFrameSaved & & ! vfb - > memoryUpdated ) {
2016-06-05 02:36:30 +00:00
// Some games will draw to some memory once, and use it as a render-to-texture later.
2017-04-09 22:19:06 +00:00
// To support this, we save the first frame to memory when we have a safe w/h.
2016-06-05 02:36:30 +00:00
// Saving each frame would be slow.
if ( ! g_Config . bDisableSlowFramebufEffects ) {
2020-08-03 21:35:40 +00:00
ReadFramebufferToMemory ( vfb , 0 , 0 , vfb - > safeWidth , vfb - > safeHeight ) ;
2017-04-09 22:10:07 +00:00
vfb - > usageFlags = ( vfb - > usageFlags | FB_USAGE_DOWNLOAD ) & ~ FB_USAGE_DOWNLOAD_CLEAR ;
2016-06-05 02:36:30 +00:00
vfb - > firstFrameSaved = true ;
vfb - > safeWidth = 0 ;
vfb - > safeHeight = 0 ;
}
}
}
2017-03-05 12:59:16 +00:00
void FramebufferManagerCommon : : SetViewport2D ( int x , int y , int w , int h ) {
Draw : : Viewport vp { ( float ) x , ( float ) y , ( float ) w , ( float ) h , 0.0f , 1.0f } ;
draw_ - > SetViewports ( 1 , & vp ) ;
}
2020-03-01 21:55:28 +00:00
void FramebufferManagerCommon : : CopyDisplayToOutput ( bool reallyDirty ) {
2017-11-05 16:13:43 +00:00
DownloadFramebufferOnSwitch ( currentRenderVfb_ ) ;
2018-07-28 09:09:01 +00:00
shaderManager_ - > DirtyLastShader ( ) ;
2017-02-15 22:24:25 +00:00
currentRenderVfb_ = 0 ;
if ( displayFramebufPtr_ = = 0 ) {
2020-07-04 18:57:05 +00:00
if ( Core_IsStepping ( ) )
2020-05-12 02:25:33 +00:00
VERBOSE_LOG ( FRAMEBUF , " Display disabled, displaying only black " ) ;
else
DEBUG_LOG ( FRAMEBUF , " Display disabled, displaying only black " ) ;
2017-02-15 22:24:25 +00:00
// No framebuffer to display! Clear to black.
2017-05-16 15:20:22 +00:00
if ( useBufferedRendering_ ) {
2020-05-21 09:24:05 +00:00
draw_ - > BindFramebufferAsRenderTarget ( nullptr , { Draw : : RPAction : : CLEAR , Draw : : RPAction : : CLEAR , Draw : : RPAction : : CLEAR } , " CopyDisplayToOutput " ) ;
2017-05-16 15:20:22 +00:00
}
2020-05-24 17:21:46 +00:00
gstate_c . Dirty ( DIRTY_VIEWPORTSCISSOR_STATE ) ;
2017-02-15 22:24:25 +00:00
return ;
}
u32 offsetX = 0 ;
u32 offsetY = 0 ;
2020-03-01 21:55:28 +00:00
// If it's not really dirty, we're probably frameskipping. Use the last working one.
u32 fbaddr = reallyDirty ? displayFramebufPtr_ : prevDisplayFramebufPtr_ ;
prevDisplayFramebufPtr_ = fbaddr ;
VirtualFramebuffer * vfb = GetVFBAt ( fbaddr ) ;
2017-02-15 22:24:25 +00:00
if ( ! vfb ) {
2018-11-11 09:54:28 +00:00
// Let's search for a framebuf within this range. Note that we also look for
2019-09-17 12:45:40 +00:00
// "framebuffers" sitting in RAM (created from block transfer or similar) so we only take off the kernel
// and uncached bits of the address when comparing.
2020-03-01 21:55:28 +00:00
const u32 addr = fbaddr & 0x3FFFFFFF ;
2017-02-15 22:24:25 +00:00
for ( size_t i = 0 ; i < vfbs_ . size ( ) ; + + i ) {
VirtualFramebuffer * v = vfbs_ [ i ] ;
2018-11-11 09:54:28 +00:00
const u32 v_addr = v - > fb_address & 0x3FFFFFFF ;
2019-09-17 12:45:40 +00:00
const u32 v_size = ColorBufferByteSize ( v ) ;
2017-02-15 22:24:25 +00:00
if ( addr > = v_addr & & addr < v_addr + v_size ) {
const u32 dstBpp = v - > format = = GE_FORMAT_8888 ? 4 : 2 ;
const u32 v_offsetX = ( ( addr - v_addr ) / dstBpp ) % v - > fb_stride ;
const u32 v_offsetY = ( ( addr - v_addr ) / dstBpp ) / v - > fb_stride ;
// We have enough space there for the display, right?
if ( v_offsetX + 480 > ( u32 ) v - > fb_stride | | v - > bufferHeight < v_offsetY + 272 ) {
continue ;
}
// Check for the closest one.
if ( offsetY = = 0 | | offsetY > v_offsetY ) {
offsetX = v_offsetX ;
offsetY = v_offsetY ;
vfb = v ;
}
}
}
if ( vfb ) {
// Okay, we found one above.
2020-08-15 09:51:22 +00:00
// Log should be "Displaying from framebuf" but not worth changing the report.
INFO_LOG_REPORT_ONCE ( displayoffset , FRAMEBUF , " Rendering from framebuf with offset %08x -> %08x+%dx%d " , addr , vfb - > fb_address , offsetX , offsetY ) ;
2017-02-15 22:24:25 +00:00
}
}
if ( vfb & & vfb - > format ! = displayFormat_ ) {
if ( vfb - > last_frame_render + FBO_OLD_AGE < gpuStats . numFlips ) {
// The game probably switched formats on us.
vfb - > format = displayFormat_ ;
} else {
vfb = 0 ;
}
}
if ( ! vfb ) {
2020-03-01 21:55:28 +00:00
if ( Memory : : IsValidAddress ( fbaddr ) ) {
2017-02-15 22:24:25 +00:00
// The game is displaying something directly from RAM. In GTA, it's decoded video.
if ( ! vfb ) {
2020-05-12 02:11:43 +00:00
DrawFramebufferToOutput ( Memory : : GetPointer ( fbaddr ) , displayFormat_ , displayStride_ ) ;
2017-02-15 22:24:25 +00:00
return ;
}
} else {
2020-03-01 21:55:28 +00:00
DEBUG_LOG ( FRAMEBUF , " Found no FBO to display! displayFBPtr = %08x " , fbaddr ) ;
2017-02-15 22:24:25 +00:00
// No framebuffer to display! Clear to black.
2017-05-16 14:00:34 +00:00
if ( useBufferedRendering_ ) {
// Bind and clear the backbuffer. This should be the first time during the frame that it's bound.
2020-05-21 09:24:05 +00:00
draw_ - > BindFramebufferAsRenderTarget ( nullptr , { Draw : : RPAction : : CLEAR , Draw : : RPAction : : CLEAR , Draw : : RPAction : : CLEAR } , " CopyDisplayToOutput_NoFBO " ) ;
2017-05-16 14:00:34 +00:00
}
2020-05-24 17:21:46 +00:00
gstate_c . Dirty ( DIRTY_VIEWPORTSCISSOR_STATE ) ;
2017-02-15 22:24:25 +00:00
return ;
}
}
vfb - > usageFlags | = FB_USAGE_DISPLAYED_FRAMEBUFFER ;
vfb - > last_frame_displayed = gpuStats . numFlips ;
vfb - > dirtyAfterDisplay = false ;
vfb - > reallyDirtyAfterDisplay = false ;
if ( prevDisplayFramebuf_ ! = displayFramebuf_ ) {
prevPrevDisplayFramebuf_ = prevDisplayFramebuf_ ;
}
if ( displayFramebuf_ ! = vfb ) {
prevDisplayFramebuf_ = displayFramebuf_ ;
}
displayFramebuf_ = vfb ;
if ( vfb - > fbo ) {
2020-07-04 18:57:05 +00:00
if ( Core_IsStepping ( ) )
2018-10-07 21:40:10 +00:00
VERBOSE_LOG ( FRAMEBUF , " Displaying FBO %08x " , vfb - > fb_address ) ;
else
DEBUG_LOG ( FRAMEBUF , " Displaying FBO %08x " , vfb - > fb_address ) ;
2017-02-15 22:24:25 +00:00
// TODO ES3: Use glInvalidateFramebuffer to discard depth/stencil data at the end of frame.
float u0 = offsetX / ( float ) vfb - > bufferWidth ;
float v0 = offsetY / ( float ) vfb - > bufferHeight ;
float u1 = ( 480.0f + offsetX ) / ( float ) vfb - > bufferWidth ;
float v1 = ( 272.0f + offsetY ) / ( float ) vfb - > bufferHeight ;
2020-05-10 23:53:15 +00:00
textureCache_ - > ForgetLastTexture ( ) ;
2020-05-12 02:25:33 +00:00
int uvRotation = useBufferedRendering_ ? g_Config . iInternalScreenRotation : ROTATION_LOCKED_HORIZONTAL ;
2020-05-10 14:29:28 +00:00
OutputFlags flags = g_Config . iBufFilter = = SCALE_LINEAR ? OutputFlags : : LINEAR : OutputFlags : : NEAREST ;
if ( needBackBufferYSwap_ ) {
flags | = OutputFlags : : BACKBUFFER_FLIPPED ;
}
2020-05-12 06:28:50 +00:00
// DrawActiveTexture reverses these, probably to match "up".
if ( GetGPUBackend ( ) = = GPUBackend : : DIRECT3D9 | | GetGPUBackend ( ) = = GPUBackend : : DIRECT3D11 ) {
flags | = OutputFlags : : POSITION_FLIPPED ;
}
2020-05-10 14:29:28 +00:00
2020-05-10 23:53:15 +00:00
int actualWidth = ( vfb - > bufferWidth * vfb - > renderWidth ) / vfb - > width ;
int actualHeight = ( vfb - > bufferHeight * vfb - > renderHeight ) / vfb - > height ;
2020-05-16 06:41:13 +00:00
presentation_ - > UpdateUniforms ( textureCache_ - > VideoIsPlaying ( ) ) ;
presentation_ - > SourceFramebuffer ( vfb - > fbo , actualWidth , actualHeight ) ;
presentation_ - > CopyToOutput ( flags , uvRotation , u0 , v0 , u1 , v1 ) ;
2020-05-10 07:38:19 +00:00
} else if ( useBufferedRendering_ ) {
2017-03-13 11:32:21 +00:00
WARN_LOG ( FRAMEBUF , " Current VFB lacks an FBO: %08x " , vfb - > fb_address ) ;
}
2018-01-20 15:57:38 +00:00
// This may get called mid-draw if the game uses an immediate flip.
2020-05-10 23:53:15 +00:00
// PresentationCommon sets all kinds of state, we can't rely on anything.
gstate_c . Dirty ( DIRTY_ALL ) ;
2017-02-15 22:24:25 +00:00
}
2017-02-06 23:42:39 +00:00
void FramebufferManagerCommon : : DecimateFBOs ( ) {
currentRenderVfb_ = 0 ;
2017-10-25 18:28:12 +00:00
for ( auto iter : fbosToDelete_ ) {
2017-11-05 16:13:18 +00:00
iter - > Release ( ) ;
2017-10-25 18:28:12 +00:00
}
fbosToDelete_ . clear ( ) ;
2017-02-06 23:42:39 +00:00
for ( size_t i = 0 ; i < vfbs_ . size ( ) ; + + i ) {
VirtualFramebuffer * vfb = vfbs_ [ i ] ;
int age = frameLastFramebufUsed_ - std : : max ( vfb - > last_frame_render , vfb - > last_frame_used ) ;
if ( ShouldDownloadFramebuffer ( vfb ) & & age = = 0 & & ! vfb - > memoryUpdated ) {
2020-08-03 21:35:40 +00:00
ReadFramebufferToMemory ( vfb , 0 , 0 , vfb - > width , vfb - > height ) ;
2017-04-09 22:10:07 +00:00
vfb - > usageFlags = ( vfb - > usageFlags | FB_USAGE_DOWNLOAD ) & ~ FB_USAGE_DOWNLOAD_CLEAR ;
2019-06-02 18:48:46 +00:00
vfb - > firstFrameSaved = true ;
2017-02-06 23:42:39 +00:00
}
// Let's also "decimate" the usageFlags.
UpdateFramebufUsage ( vfb ) ;
if ( vfb ! = displayFramebuf_ & & vfb ! = prevDisplayFramebuf_ & & vfb ! = prevPrevDisplayFramebuf_ ) {
if ( age > FBO_OLD_AGE ) {
2017-03-13 11:32:21 +00:00
INFO_LOG ( FRAMEBUF , " Decimating FBO for %08x (%i x %i x %i), age %i " , vfb - > fb_address , vfb - > width , vfb - > height , vfb - > format , age ) ;
2017-02-06 23:42:39 +00:00
DestroyFramebuf ( vfb ) ;
vfbs_ . erase ( vfbs_ . begin ( ) + i - - ) ;
}
}
}
for ( auto it = tempFBOs_ . begin ( ) ; it ! = tempFBOs_ . end ( ) ; ) {
int age = frameLastFramebufUsed_ - it - > second . last_frame_used ;
if ( age > FBO_OLD_AGE ) {
2017-02-17 18:22:41 +00:00
it - > second . fbo - > Release ( ) ;
2018-06-03 15:08:45 +00:00
it = tempFBOs_ . erase ( it ) ;
2017-02-06 23:42:39 +00:00
} else {
+ + it ;
}
}
// Do the same for ReadFramebuffersToMemory's VFBs
for ( size_t i = 0 ; i < bvfbs_ . size ( ) ; + + i ) {
VirtualFramebuffer * vfb = bvfbs_ [ i ] ;
int age = frameLastFramebufUsed_ - vfb - > last_frame_render ;
if ( age > FBO_OLD_AGE ) {
2017-03-13 11:32:21 +00:00
INFO_LOG ( FRAMEBUF , " Decimating FBO for %08x (%i x %i x %i), age %i " , vfb - > fb_address , vfb - > width , vfb - > height , vfb - > format , age ) ;
2017-02-06 23:42:39 +00:00
DestroyFramebuf ( vfb ) ;
bvfbs_ . erase ( bvfbs_ . begin ( ) + i - - ) ;
}
}
}
2017-02-06 23:38:12 +00:00
2017-11-04 10:41:44 +00:00
void FramebufferManagerCommon : : ResizeFramebufFBO ( VirtualFramebuffer * vfb , int w , int h , bool force , bool skipCopy ) {
2020-08-15 22:38:55 +00:00
_dbg_assert_ ( w > 0 ) ;
_dbg_assert_ ( h > 0 ) ;
2017-02-06 23:38:12 +00:00
VirtualFramebuffer old = * vfb ;
2017-10-25 19:19:42 +00:00
int oldWidth = vfb - > bufferWidth ;
int oldHeight = vfb - > bufferHeight ;
2017-02-06 23:38:12 +00:00
if ( force ) {
vfb - > bufferWidth = w ;
vfb - > bufferHeight = h ;
} else {
if ( vfb - > bufferWidth > = w & & vfb - > bufferHeight > = h ) {
return ;
}
// In case it gets thin and wide, don't resize down either side.
2017-11-04 10:41:44 +00:00
vfb - > bufferWidth = std : : max ( ( int ) vfb - > bufferWidth , w ) ;
vfb - > bufferHeight = std : : max ( ( int ) vfb - > bufferHeight , h ) ;
2017-02-06 23:38:12 +00:00
}
SetRenderSize ( vfb ) ;
2018-12-14 13:01:08 +00:00
// During hardware rendering, we always render at full color depth even if the game wouldn't on real hardware.
// It's not worth the trouble trying to support lower bit-depth rendering, just
// more cases to test that nobody will ever use.
vfb - > colorDepth = Draw : : FBO_8888 ;
2017-02-06 23:38:12 +00:00
textureCache_ - > ForgetLastTexture ( ) ;
if ( ! useBufferedRendering_ ) {
if ( vfb - > fbo ) {
2017-11-05 20:45:02 +00:00
vfb - > fbo - > Release ( ) ;
2017-02-06 23:38:12 +00:00
vfb - > fbo = nullptr ;
}
return ;
}
2017-04-24 16:30:04 +00:00
if ( ! old . fbo & & vfb - > last_frame_failed ! = 0 & & vfb - > last_frame_failed - gpuStats . numFlips < 63 ) {
// Don't constantly retry FBOs which failed to create.
return ;
}
2017-02-06 23:38:12 +00:00
2018-07-28 09:09:01 +00:00
shaderManager_ - > DirtyLastShader ( ) ;
2020-08-27 08:02:50 +00:00
char tag [ 256 ] ;
snprintf ( tag , sizeof ( tag ) , " %08x_%08x_%dx%d " , vfb - > fb_address , vfb - > z_address , w , h ) ;
vfb - > fbo = draw_ - > CreateFramebuffer ( { vfb - > renderWidth , vfb - > renderHeight , 1 , 1 , true , ( Draw : : FBColorDepth ) vfb - > colorDepth , tag } ) ;
2017-02-06 23:38:12 +00:00
if ( old . fbo ) {
2020-08-27 08:02:50 +00:00
INFO_LOG ( FRAMEBUF , " Resizing FBO for %08x : %dx%dx%s " , vfb - > fb_address , w , h , GeBufferFormatToString ( vfb - > format ) ) ;
2017-02-06 23:38:12 +00:00
if ( vfb - > fbo ) {
2020-05-21 09:24:05 +00:00
draw_ - > BindFramebufferAsRenderTarget ( vfb - > fbo , { Draw : : RPAction : : CLEAR , Draw : : RPAction : : CLEAR , Draw : : RPAction : : CLEAR } , " ResizeFramebufFBO " ) ;
2019-02-08 14:02:31 +00:00
if ( ! skipCopy ) {
2017-10-25 19:19:42 +00:00
BlitFramebuffer ( vfb , 0 , 0 , & old , 0 , 0 , std : : min ( ( u16 ) oldWidth , std : : min ( vfb - > bufferWidth , vfb - > width ) ) , std : : min ( ( u16 ) oldHeight , std : : min ( vfb - > height , vfb - > bufferHeight ) ) , 0 ) ;
2017-02-06 23:38:12 +00:00
}
}
2017-10-25 18:28:12 +00:00
fbosToDelete_ . push_back ( old . fbo ) ;
2017-05-23 09:12:10 +00:00
if ( needGLESRebinds_ ) {
2020-05-21 09:24:05 +00:00
draw_ - > BindFramebufferAsRenderTarget ( vfb - > fbo , { Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP } , " ResizeFramebufFBO " ) ;
2017-05-23 09:12:10 +00:00
}
2017-05-16 14:00:34 +00:00
} else {
2020-05-21 09:24:05 +00:00
draw_ - > BindFramebufferAsRenderTarget ( vfb - > fbo , { Draw : : RPAction : : CLEAR , Draw : : RPAction : : CLEAR , Draw : : RPAction : : CLEAR } , " ResizeFramebufFBO " ) ;
2017-02-06 23:38:12 +00:00
}
if ( ! vfb - > fbo ) {
2020-08-27 08:02:50 +00:00
ERROR_LOG ( FRAMEBUF , " Error creating FBO during resize! %dx%d " , vfb - > renderWidth , vfb - > renderHeight ) ;
2017-04-24 16:30:04 +00:00
vfb - > last_frame_failed = gpuStats . numFlips ;
2017-02-06 23:38:12 +00:00
}
}
2020-05-10 21:09:40 +00:00
// This is called from detected memcopies and framebuffer initialization from VRAM. Not block transfers.
2018-11-11 21:50:15 +00:00
// MotoGP goes this path so we need to catch those copies here.
2015-08-05 10:13:14 +00:00
bool FramebufferManagerCommon : : NotifyFramebufferCopy ( u32 src , u32 dst , int size , bool isMemset , u32 skipDrawReason ) {
2017-12-25 19:17:59 +00:00
if ( size = = 0 ) {
2014-09-13 22:40:55 +00:00
return false ;
}
dst & = 0x3FFFFFFF ;
src & = 0x3FFFFFFF ;
VirtualFramebuffer * dstBuffer = 0 ;
VirtualFramebuffer * srcBuffer = 0 ;
u32 dstY = ( u32 ) - 1 ;
u32 dstH = 0 ;
u32 srcY = ( u32 ) - 1 ;
u32 srcH = 0 ;
for ( size_t i = 0 ; i < vfbs_ . size ( ) ; + + i ) {
VirtualFramebuffer * vfb = vfbs_ [ i ] ;
2014-09-22 04:35:39 +00:00
if ( vfb - > fb_stride = = 0 ) {
continue ;
}
2018-11-11 09:54:28 +00:00
// We only remove the kernel and uncached bits when comparing.
const u32 vfb_address = vfb - > fb_address & 0x3FFFFFFF ;
2019-09-17 12:45:40 +00:00
const u32 vfb_size = ColorBufferByteSize ( vfb ) ;
2014-09-13 22:40:55 +00:00
const u32 vfb_bpp = vfb - > format = = GE_FORMAT_8888 ? 4 : 2 ;
const u32 vfb_byteStride = vfb - > fb_stride * vfb_bpp ;
const int vfb_byteWidth = vfb - > width * vfb_bpp ;
if ( dst > = vfb_address & & ( dst + size < = vfb_address + vfb_size | | dst = = vfb_address ) ) {
const u32 offset = dst - vfb_address ;
const u32 yOffset = offset / vfb_byteStride ;
if ( ( offset % vfb_byteStride ) = = 0 & & ( size = = vfb_byteWidth | | ( size % vfb_byteStride ) = = 0 ) & & yOffset < dstY ) {
dstBuffer = vfb ;
dstY = yOffset ;
dstH = size = = vfb_byteWidth ? 1 : std : : min ( ( u32 ) size / vfb_byteStride , ( u32 ) vfb - > height ) ;
}
}
if ( src > = vfb_address & & ( src + size < = vfb_address + vfb_size | | src = = vfb_address ) ) {
const u32 offset = src - vfb_address ;
const u32 yOffset = offset / vfb_byteStride ;
if ( ( offset % vfb_byteStride ) = = 0 & & ( size = = vfb_byteWidth | | ( size % vfb_byteStride ) = = 0 ) & & yOffset < srcY ) {
srcBuffer = vfb ;
srcY = yOffset ;
srcH = size = = vfb_byteWidth ? 1 : std : : min ( ( u32 ) size / vfb_byteStride , ( u32 ) vfb - > height ) ;
2015-04-12 18:02:12 +00:00
} else if ( ( offset % vfb_byteStride ) = = 0 & & size = = vfb - > fb_stride & & yOffset < srcY ) {
// Valkyrie Profile reads 512 bytes at a time, rather than 2048. So, let's whitelist fb_stride also.
srcBuffer = vfb ;
srcY = yOffset ;
srcH = 1 ;
2015-04-28 07:08:00 +00:00
} else if ( yOffset = = 0 & & yOffset < srcY ) {
// Okay, last try - it might be a clut.
if ( vfb - > usageFlags & FB_USAGE_CLUT ) {
srcBuffer = vfb ;
srcY = yOffset ;
srcH = 1 ;
}
2014-09-13 22:40:55 +00:00
}
}
}
if ( ! useBufferedRendering_ ) {
// If we're copying into a recently used display buf, it's probably destined for the screen.
if ( srcBuffer | | ( dstBuffer ! = displayFramebuf_ & & dstBuffer ! = prevDisplayFramebuf_ ) ) {
return false ;
}
}
2018-11-11 21:50:15 +00:00
if ( ! dstBuffer & & srcBuffer & & PSP_CoreParameter ( ) . compat . flags ( ) . BlockTransferAllowCreateFB ) {
dstBuffer = CreateRAMFramebuffer ( dst , srcBuffer - > width , srcBuffer - > height , srcBuffer - > fb_stride , srcBuffer - > format ) ;
dstY = 0 ;
}
if ( dstBuffer ) {
dstBuffer - > last_frame_used = gpuStats . numFlips ;
}
2014-09-13 22:40:55 +00:00
if ( dstBuffer & & srcBuffer & & ! isMemset ) {
if ( srcBuffer = = dstBuffer ) {
2020-08-10 07:16:28 +00:00
WARN_LOG_ONCE ( dstsrccpy , G3D , " Intra-buffer memcpy (not supported) %08x -> %08x (size: %x) " , src , dst , size ) ;
2014-09-13 22:40:55 +00:00
} else {
2020-08-10 07:16:28 +00:00
WARN_LOG_ONCE ( dstnotsrccpy , G3D , " Inter-buffer memcpy %08x -> %08x (size: %x) " , src , dst , size ) ;
2014-09-13 22:40:55 +00:00
// Just do the blit!
2019-02-08 13:50:47 +00:00
BlitFramebuffer ( dstBuffer , 0 , dstY , srcBuffer , 0 , srcY , srcBuffer - > width , srcH , 0 ) ;
SetColorUpdated ( dstBuffer , skipDrawReason ) ;
2020-06-02 07:51:38 +00:00
RebindFramebuffer ( " RebindFramebuffer - Inter-buffer memcpy " ) ;
2014-09-13 22:40:55 +00:00
}
return false ;
} else if ( dstBuffer ) {
2017-12-27 03:34:57 +00:00
if ( isMemset ) {
gpuStats . numClears + + ;
}
2017-11-22 11:24:05 +00:00
WARN_LOG_ONCE ( btucpy , G3D , " Memcpy fbo upload %08x -> %08x (size: %x) " , src , dst , size ) ;
2019-02-08 13:50:47 +00:00
FlushBeforeCopy ( ) ;
const u8 * srcBase = Memory : : GetPointerUnchecked ( src ) ;
DrawPixels ( dstBuffer , 0 , dstY , srcBase , dstBuffer - > format , dstBuffer - > fb_stride , dstBuffer - > width , dstH ) ;
SetColorUpdated ( dstBuffer , skipDrawReason ) ;
2020-06-02 07:51:38 +00:00
RebindFramebuffer ( " RebindFramebuffer - Memcpy fbo upload " ) ;
2019-02-08 13:50:47 +00:00
// This is a memcpy, let's still copy just in case.
2014-09-13 22:40:55 +00:00
return false ;
} else if ( srcBuffer ) {
WARN_LOG_ONCE ( btdcpy , G3D , " Memcpy fbo download %08x -> %08x " , src , dst ) ;
FlushBeforeCopy ( ) ;
if ( srcH = = 0 | | srcY + srcH > srcBuffer - > bufferHeight ) {
2020-08-10 07:16:28 +00:00
WARN_LOG_ONCE ( btdcpyheight , G3D , " Memcpy fbo download %08x -> %08x skipped, %d+%d is taller than %d " , src , dst , srcY , srcH , srcBuffer - > bufferHeight ) ;
2017-11-10 15:32:56 +00:00
} else if ( g_Config . bBlockTransferGPU & & ! srcBuffer - > memoryUpdated & & ! PSP_CoreParameter ( ) . compat . flags ( ) . DisableReadbacks ) {
2020-08-03 21:35:40 +00:00
ReadFramebufferToMemory ( srcBuffer , 0 , srcY , srcBuffer - > width , srcH ) ;
2017-04-09 22:10:07 +00:00
srcBuffer - > usageFlags = ( srcBuffer - > usageFlags | FB_USAGE_DOWNLOAD ) & ~ FB_USAGE_DOWNLOAD_CLEAR ;
2014-09-13 22:40:55 +00:00
}
return false ;
} else {
return false ;
}
}
2018-11-04 23:28:01 +00:00
// Can't be const, in case it has to create a vfb unfortunately.
void FramebufferManagerCommon : : FindTransferFramebuffers ( VirtualFramebuffer * & dstBuffer , VirtualFramebuffer * & srcBuffer , u32 dstBasePtr , int dstStride , int & dstX , int & dstY , u32 srcBasePtr , int srcStride , int & srcX , int & srcY , int & srcWidth , int & srcHeight , int & dstWidth , int & dstHeight , int bpp ) {
2014-09-13 22:40:55 +00:00
u32 dstYOffset = - 1 ;
u32 dstXOffset = - 1 ;
u32 srcYOffset = - 1 ;
u32 srcXOffset = - 1 ;
int width = srcWidth ;
int height = srcHeight ;
dstBasePtr & = 0x3FFFFFFF ;
srcBasePtr & = 0x3FFFFFFF ;
for ( size_t i = 0 ; i < vfbs_ . size ( ) ; + + i ) {
VirtualFramebuffer * vfb = vfbs_ [ i ] ;
2018-11-11 09:54:28 +00:00
const u32 vfb_address = vfb - > fb_address & 0x3FFFFFFF ;
2019-09-17 12:45:40 +00:00
const u32 vfb_size = ColorBufferByteSize ( vfb ) ;
2014-09-13 22:40:55 +00:00
const u32 vfb_bpp = vfb - > format = = GE_FORMAT_8888 ? 4 : 2 ;
const u32 vfb_byteStride = vfb - > fb_stride * vfb_bpp ;
const u32 vfb_byteWidth = vfb - > width * vfb_bpp ;
// These heuristics are a bit annoying.
// The goal is to avoid using GPU block transfers for things that ought to be memory.
// Maybe we should even check for textures at these places instead?
if ( vfb_address < = dstBasePtr & & dstBasePtr < vfb_address + vfb_size ) {
const u32 byteOffset = dstBasePtr - vfb_address ;
const u32 byteStride = dstStride * bpp ;
const u32 yOffset = byteOffset / byteStride ;
2018-02-28 00:01:45 +00:00
2014-09-13 22:40:55 +00:00
// Some games use mismatching bitdepths. But make sure the stride matches.
// If it doesn't, generally this means we detected the framebuffer with too large a height.
2019-07-28 12:31:49 +00:00
// Use bufferHeight in case of buffers that resize up and down often per frame (Valkyrie Profile.)
bool match = yOffset < dstYOffset & & ( int ) yOffset < = ( int ) vfb - > bufferHeight - dstHeight ;
2014-09-13 22:40:55 +00:00
if ( match & & vfb_byteStride ! = byteStride ) {
// Grand Knights History copies with a mismatching stride but a full line at a time.
// Makes it hard to detect the wrong transfers in e.g. God of War.
if ( width ! = dstStride | | ( byteStride * height ! = vfb_byteStride & & byteStride * height ! = vfb_byteWidth ) ) {
2015-04-28 07:08:00 +00:00
// However, some other games write cluts to framebuffers.
// Let's catch this and upload. Otherwise reject the match.
match = ( vfb - > usageFlags & FB_USAGE_CLUT ) ! = 0 ;
if ( match ) {
dstWidth = byteStride * height / vfb_bpp ;
dstHeight = 1 ;
}
2014-09-13 22:40:55 +00:00
} else {
dstWidth = byteStride * height / vfb_bpp ;
dstHeight = 1 ;
}
} else if ( match ) {
dstWidth = width ;
dstHeight = height ;
}
if ( match ) {
dstYOffset = yOffset ;
2015-04-08 18:44:45 +00:00
dstXOffset = dstStride = = 0 ? 0 : ( byteOffset / bpp ) % dstStride ;
2014-09-13 22:40:55 +00:00
dstBuffer = vfb ;
}
}
if ( vfb_address < = srcBasePtr & & srcBasePtr < vfb_address + vfb_size ) {
const u32 byteOffset = srcBasePtr - vfb_address ;
const u32 byteStride = srcStride * bpp ;
const u32 yOffset = byteOffset / byteStride ;
2019-07-28 12:31:49 +00:00
bool match = yOffset < srcYOffset & & ( int ) yOffset < = ( int ) vfb - > bufferHeight - srcHeight ;
2014-09-13 22:40:55 +00:00
if ( match & & vfb_byteStride ! = byteStride ) {
if ( width ! = srcStride | | ( byteStride * height ! = vfb_byteStride & & byteStride * height ! = vfb_byteWidth ) ) {
match = false ;
} else {
srcWidth = byteStride * height / vfb_bpp ;
srcHeight = 1 ;
}
} else if ( match ) {
srcWidth = width ;
srcHeight = height ;
}
if ( match ) {
srcYOffset = yOffset ;
2015-01-19 16:43:43 +00:00
srcXOffset = srcStride = = 0 ? 0 : ( byteOffset / bpp ) % srcStride ;
2014-09-13 22:40:55 +00:00
srcBuffer = vfb ;
}
}
}
2020-09-04 07:01:42 +00:00
if ( srcBuffer & & ! dstBuffer & & PSP_CoreParameter ( ) . compat . flags ( ) . BlockTransferAllowCreateFB ) {
2020-08-29 10:42:36 +00:00
GEBufferFormat ramFormat ;
// Try to guess the appropriate format. We only know the bpp from the block transfer command (16 or 32 bit).
if ( bpp = = 4 ) {
// Only one possibility unless it's doing split pixel tricks (which we could detect through stride maybe).
ramFormat = GE_FORMAT_8888 ;
} else if ( srcBuffer - > format ! = GE_FORMAT_8888 ) {
// We guess that the game will interpret the data the same as it was in the source of the copy.
// Seems like a likely good guess, and works in Test Drive Unlimited.
ramFormat = srcBuffer - > format ;
} else {
// No info left - just fall back to something. But this is definitely split pixel tricks.
ramFormat = GE_FORMAT_5551 ;
}
2018-11-11 21:50:15 +00:00
dstBuffer = CreateRAMFramebuffer ( dstBasePtr , dstWidth , dstHeight , dstStride , ramFormat ) ;
2018-11-04 23:28:01 +00:00
}
2018-11-11 21:50:15 +00:00
if ( dstBuffer )
dstBuffer - > last_frame_used = gpuStats . numFlips ;
2018-11-04 23:28:01 +00:00
2014-09-13 22:40:55 +00:00
if ( dstYOffset ! = ( u32 ) - 1 ) {
dstY + = dstYOffset ;
dstX + = dstXOffset ;
}
if ( srcYOffset ! = ( u32 ) - 1 ) {
srcY + = srcYOffset ;
srcX + = srcXOffset ;
}
}
2018-11-11 21:50:15 +00:00
VirtualFramebuffer * FramebufferManagerCommon : : CreateRAMFramebuffer ( uint32_t fbAddress , int width , int height , int stride , GEBufferFormat format ) {
float renderWidthFactor = renderWidth_ / 480.0f ;
float renderHeightFactor = renderHeight_ / 272.0f ;
2020-08-04 12:45:14 +00:00
INFO_LOG ( G3D , " Creating RAM framebuffer at %08x (%dx%d, stride %d, format %d) " , fbAddress , width , height , stride , format ) ;
2019-09-24 21:10:18 +00:00
2018-11-11 21:50:15 +00:00
// A target for the destination is missing - so just create one!
// Make sure this one would be found by the algorithm above so we wouldn't
// create a new one each frame.
VirtualFramebuffer * vfb = new VirtualFramebuffer { } ;
vfb - > fbo = nullptr ;
vfb - > fb_address = fbAddress ; // NOTE - not necessarily in VRAM!
vfb - > fb_stride = stride ;
vfb - > z_address = 0 ; // marks that if anyone tries to render to this framebuffer, it should be dropped and recreated.
vfb - > z_stride = 0 ;
vfb - > width = std : : max ( width , stride ) ;
vfb - > height = height ;
vfb - > newWidth = vfb - > width ;
vfb - > newHeight = vfb - > height ;
vfb - > lastFrameNewSize = gpuStats . numFlips ;
vfb - > renderWidth = ( u16 ) ( vfb - > width * renderWidthFactor ) ;
vfb - > renderHeight = ( u16 ) ( vfb - > height * renderHeightFactor ) ;
vfb - > bufferWidth = vfb - > width ;
vfb - > bufferHeight = vfb - > height ;
vfb - > format = format ;
vfb - > drawnFormat = GE_FORMAT_8888 ;
vfb - > usageFlags = FB_USAGE_RENDERTARGET ;
SetColorUpdated ( vfb , 0 ) ;
2020-08-09 07:35:56 +00:00
char name [ 64 ] ;
snprintf ( name , sizeof ( name ) , " %08x_color_RAM " , vfb - > fb_address ) ;
2020-09-20 19:46:40 +00:00
textureCache_ - > NotifyFramebuffer ( vfb , NOTIFY_FB_CREATED ) ;
2020-08-09 07:35:56 +00:00
vfb - > fbo = draw_ - > CreateFramebuffer ( { vfb - > renderWidth , vfb - > renderHeight , 1 , 1 , true , ( Draw : : FBColorDepth ) vfb - > colorDepth , name } ) ;
2018-11-11 21:50:15 +00:00
vfbs_ . push_back ( vfb ) ;
2019-09-17 12:45:40 +00:00
2019-09-28 15:40:41 +00:00
u32 byteSize = ColorBufferByteSize ( vfb ) ;
2019-09-17 12:45:40 +00:00
if ( fbAddress + byteSize > framebufRangeEnd_ ) {
framebufRangeEnd_ = fbAddress + byteSize ;
}
2018-11-11 21:50:15 +00:00
return vfb ;
}
2017-02-14 11:42:35 +00:00
// 1:1 pixel sides buffers, we resize buffers to these before we read them back.
2016-01-05 04:40:07 +00:00
VirtualFramebuffer * FramebufferManagerCommon : : FindDownloadTempBuffer ( VirtualFramebuffer * vfb ) {
// For now we'll keep these on the same struct as the ones that can get displayed
// (and blatantly copy work already done above while at it).
2020-05-16 23:55:21 +00:00
VirtualFramebuffer * nvfb = nullptr ;
2016-01-05 04:40:07 +00:00
// We maintain a separate vector of framebuffer objects for blitting.
2020-05-16 23:55:21 +00:00
for ( VirtualFramebuffer * v : bvfbs_ ) {
2016-01-05 04:40:07 +00:00
if ( v - > fb_address = = vfb - > fb_address & & v - > format = = vfb - > format ) {
if ( v - > bufferWidth = = vfb - > bufferWidth & & v - > bufferHeight = = vfb - > bufferHeight ) {
nvfb = v ;
v - > fb_stride = vfb - > fb_stride ;
v - > width = vfb - > width ;
v - > height = vfb - > height ;
break ;
}
}
}
// Create a new fbo if none was found for the size
if ( ! nvfb ) {
nvfb = new VirtualFramebuffer ( ) ;
2016-05-20 03:55:34 +00:00
memset ( nvfb , 0 , sizeof ( VirtualFramebuffer ) ) ;
2016-01-05 04:40:07 +00:00
nvfb - > fbo = nullptr ;
nvfb - > fb_address = vfb - > fb_address ;
nvfb - > fb_stride = vfb - > fb_stride ;
nvfb - > z_address = vfb - > z_address ;
nvfb - > z_stride = vfb - > z_stride ;
nvfb - > width = vfb - > width ;
nvfb - > height = vfb - > height ;
nvfb - > renderWidth = vfb - > bufferWidth ;
nvfb - > renderHeight = vfb - > bufferHeight ;
nvfb - > bufferWidth = vfb - > bufferWidth ;
nvfb - > bufferHeight = vfb - > bufferHeight ;
nvfb - > format = vfb - > format ;
nvfb - > drawnWidth = vfb - > drawnWidth ;
nvfb - > drawnHeight = vfb - > drawnHeight ;
nvfb - > drawnFormat = vfb - > format ;
2016-01-05 04:51:43 +00:00
nvfb - > colorDepth = vfb - > colorDepth ;
if ( ! CreateDownloadTempBuffer ( nvfb ) ) {
delete nvfb ;
return nullptr ;
}
bvfbs_ . push_back ( nvfb ) ;
} else {
UpdateDownloadTempBuffer ( nvfb ) ;
2016-01-05 04:40:07 +00:00
}
nvfb - > usageFlags | = FB_USAGE_RENDERTARGET ;
nvfb - > last_frame_render = gpuStats . numFlips ;
nvfb - > dirtyAfterDisplay = true ;
return nvfb ;
}
2020-05-16 23:55:21 +00:00
bool FramebufferManagerCommon : : CreateDownloadTempBuffer ( VirtualFramebuffer * nvfb ) {
// When updating VRAM, it need to be exact format.
if ( ! gstate_c . Supports ( GPU_PREFER_CPU_DOWNLOAD ) ) {
switch ( nvfb - > format ) {
case GE_FORMAT_4444 :
nvfb - > colorDepth = Draw : : FBO_4444 ;
break ;
case GE_FORMAT_5551 :
nvfb - > colorDepth = Draw : : FBO_5551 ;
break ;
case GE_FORMAT_565 :
nvfb - > colorDepth = Draw : : FBO_565 ;
break ;
case GE_FORMAT_8888 :
default :
nvfb - > colorDepth = Draw : : FBO_8888 ;
break ;
}
}
2020-08-09 07:35:56 +00:00
char name [ 64 ] ;
snprintf ( name , sizeof ( name ) , " download_temp " ) ;
nvfb - > fbo = draw_ - > CreateFramebuffer ( { nvfb - > bufferWidth , nvfb - > bufferHeight , 1 , 1 , false , ( Draw : : FBColorDepth ) nvfb - > colorDepth , name } ) ;
2020-05-16 23:55:21 +00:00
if ( ! nvfb - > fbo ) {
2020-08-16 20:22:58 +00:00
ERROR_LOG ( FRAMEBUF , " Error creating FBO! %d x %d " , nvfb - > renderWidth , nvfb - > renderHeight ) ;
2020-05-16 23:55:21 +00:00
return false ;
}
return true ;
}
2017-04-09 22:10:07 +00:00
void FramebufferManagerCommon : : ApplyClearToMemory ( int x1 , int y1 , int x2 , int y2 , u32 clearColor ) {
if ( currentRenderVfb_ ) {
if ( ( currentRenderVfb_ - > usageFlags & FB_USAGE_DOWNLOAD_CLEAR ) ! = 0 ) {
// Already zeroed in memory.
return ;
}
}
u8 * addr = Memory : : GetPointer ( gstate . getFrameBufAddress ( ) ) ;
const int bpp = gstate . FrameBufFormat ( ) = = GE_FORMAT_8888 ? 4 : 2 ;
2017-04-09 22:12:56 +00:00
u32 clearBits = clearColor ;
if ( bpp = = 2 ) {
u16 clear16 = 0 ;
switch ( gstate . FrameBufFormat ( ) ) {
case GE_FORMAT_565 : ConvertRGBA8888ToRGB565 ( & clear16 , & clearColor , 1 ) ; break ;
case GE_FORMAT_5551 : ConvertRGBA8888ToRGBA5551 ( & clear16 , & clearColor , 1 ) ; break ;
case GE_FORMAT_4444 : ConvertRGBA8888ToRGBA4444 ( & clear16 , & clearColor , 1 ) ; break ;
2020-07-19 15:47:02 +00:00
default : _dbg_assert_ ( 0 ) ; break ;
2017-04-09 22:12:56 +00:00
}
clearBits = clear16 | ( clear16 < < 16 ) ;
}
const bool singleByteClear = ( clearBits > > 16 ) = = ( clearBits & 0xFFFF ) & & ( clearBits > > 24 ) = = ( clearBits & 0xFF ) ;
2017-04-09 22:10:07 +00:00
const int stride = gstate . FrameBufStride ( ) ;
const int width = x2 - x1 ;
// Can use memset for simple cases. Often alpha is different and gums up the works.
2017-04-09 22:12:56 +00:00
if ( singleByteClear ) {
2017-04-09 22:10:07 +00:00
const int byteStride = stride * bpp ;
const int byteWidth = width * bpp ;
addr + = x1 * bpp ;
for ( int y = y1 ; y < y2 ; + + y ) {
2017-04-09 22:12:56 +00:00
memset ( addr + y * byteStride , clearBits , byteWidth ) ;
2017-04-09 22:10:07 +00:00
}
} else {
// This will most often be true - rarely is the width not aligned.
// TODO: We should really use non-temporal stores here to avoid the cache,
// as it's unlikely that these bytes will be read.
if ( ( width & 3 ) = = 0 & & ( x1 & 3 ) = = 0 ) {
2017-04-09 22:12:56 +00:00
u64 val64 = clearBits | ( ( u64 ) clearBits < < 32 ) ;
int xstride = 8 / bpp ;
2017-04-09 22:10:07 +00:00
u64 * addr64 = ( u64 * ) addr ;
const int stride64 = stride / xstride ;
const int x1_64 = x1 / xstride ;
const int x2_64 = x2 / xstride ;
for ( int y = y1 ; y < y2 ; + + y ) {
for ( int x = x1_64 ; x < x2_64 ; + + x ) {
addr64 [ y * stride64 + x ] = val64 ;
}
}
} else if ( bpp = = 4 ) {
u32 * addr32 = ( u32 * ) addr ;
for ( int y = y1 ; y < y2 ; + + y ) {
for ( int x = x1 ; x < x2 ; + + x ) {
2017-04-09 22:12:56 +00:00
addr32 [ y * stride + x ] = clearBits ;
2017-04-09 22:10:07 +00:00
}
}
} else if ( bpp = = 2 ) {
u16 * addr16 = ( u16 * ) addr ;
for ( int y = y1 ; y < y2 ; + + y ) {
for ( int x = x1 ; x < x2 ; + + x ) {
2017-04-09 22:12:56 +00:00
addr16 [ y * stride + x ] = ( u16 ) clearBits ;
2017-04-09 22:10:07 +00:00
}
}
}
}
if ( currentRenderVfb_ ) {
// The current content is in memory now, so update the flag.
if ( x1 = = 0 & & y1 = = 0 & & x2 > = currentRenderVfb_ - > width & & y2 > = currentRenderVfb_ - > height ) {
currentRenderVfb_ - > usageFlags | = FB_USAGE_DOWNLOAD_CLEAR ;
2017-04-09 22:19:06 +00:00
currentRenderVfb_ - > memoryUpdated = true ;
2017-04-09 22:10:07 +00:00
}
}
}
2016-01-05 04:40:07 +00:00
void FramebufferManagerCommon : : OptimizeDownloadRange ( VirtualFramebuffer * vfb , int & x , int & y , int & w , int & h ) {
if ( gameUsesSequentialCopies_ ) {
// Ignore the x/y/etc., read the entire thing.
x = 0 ;
y = 0 ;
w = vfb - > width ;
h = vfb - > height ;
}
if ( x = = 0 & & y = = 0 & & w = = vfb - > width & & h = = vfb - > height ) {
// Mark it as fully downloaded until next render to it.
vfb - > memoryUpdated = true ;
2017-04-09 22:10:07 +00:00
vfb - > usageFlags | = FB_USAGE_DOWNLOAD ;
2016-01-05 04:40:07 +00:00
} else {
// Let's try to set the flag eventually, if the game copies a lot.
// Some games copy subranges very frequently.
const static int FREQUENT_SEQUENTIAL_COPIES = 3 ;
static int frameLastCopy = 0 ;
static u32 bufferLastCopy = 0 ;
static int copiesThisFrame = 0 ;
if ( frameLastCopy ! = gpuStats . numFlips | | bufferLastCopy ! = vfb - > fb_address ) {
frameLastCopy = gpuStats . numFlips ;
bufferLastCopy = vfb - > fb_address ;
copiesThisFrame = 0 ;
}
if ( + + copiesThisFrame > FREQUENT_SEQUENTIAL_COPIES ) {
gameUsesSequentialCopies_ = true ;
}
}
}
2015-08-05 10:13:14 +00:00
bool FramebufferManagerCommon : : NotifyBlockTransferBefore ( u32 dstBasePtr , int dstStride , int dstX , int dstY , u32 srcBasePtr , int srcStride , int srcX , int srcY , int width , int height , int bpp , u32 skipDrawReason ) {
2017-12-25 19:17:59 +00:00
if ( ! useBufferedRendering_ ) {
2014-09-13 22:40:55 +00:00
return false ;
}
// Skip checking if there's no framebuffers in that area.
if ( ! MayIntersectFramebuffer ( srcBasePtr ) & & ! MayIntersectFramebuffer ( dstBasePtr ) ) {
return false ;
}
VirtualFramebuffer * dstBuffer = 0 ;
VirtualFramebuffer * srcBuffer = 0 ;
int srcWidth = width ;
int srcHeight = height ;
int dstWidth = width ;
int dstHeight = height ;
FindTransferFramebuffers ( dstBuffer , srcBuffer , dstBasePtr , dstStride , dstX , dstY , srcBasePtr , srcStride , srcX , srcY , srcWidth , srcHeight , dstWidth , dstHeight , bpp ) ;
if ( dstBuffer & & srcBuffer ) {
if ( srcBuffer = = dstBuffer ) {
if ( srcX ! = dstX | | srcY ! = dstY ) {
2020-08-16 20:38:15 +00:00
WARN_LOG_N_TIMES ( dstsrc , 100 , G3D , " Intra-buffer block transfer %08x (x:%d y:%d stride:%d) -> %08x (x:%d y:%d stride:%d) (%dx%d %dbpp) " ,
2020-08-16 20:22:58 +00:00
srcBasePtr , srcX , srcY , srcStride ,
dstBasePtr , dstX , dstY , dstStride ,
width , height , bpp ) ;
2019-02-08 13:50:47 +00:00
FlushBeforeCopy ( ) ;
BlitFramebuffer ( dstBuffer , dstX , dstY , srcBuffer , srcX , srcY , dstWidth , dstHeight , bpp ) ;
2020-06-02 07:51:38 +00:00
RebindFramebuffer ( " rebind after intra block transfer " ) ;
2019-02-08 13:50:47 +00:00
SetColorUpdated ( dstBuffer , skipDrawReason ) ;
return true ;
2014-09-13 22:40:55 +00:00
} else {
// Ignore, nothing to do. Tales of Phantasia X does this by accident.
2019-02-08 13:50:47 +00:00
return true ;
2014-09-13 22:40:55 +00:00
}
} else {
2020-08-16 20:38:15 +00:00
WARN_LOG_N_TIMES ( dstnotsrc , 100 , G3D , " Inter-buffer block transfer %08x (x:%d y:%d stride:%d) -> %08x (x:%d y:%d stride:%d) (%dx%d %dbpp) " ,
2020-08-16 20:22:58 +00:00
srcBasePtr , srcX , srcY , srcStride ,
dstBasePtr , dstX , dstY , dstStride ,
width , height , bpp ) ;
2014-09-13 22:40:55 +00:00
// Just do the blit!
2019-02-08 13:50:47 +00:00
FlushBeforeCopy ( ) ;
BlitFramebuffer ( dstBuffer , dstX , dstY , srcBuffer , srcX , srcY , dstWidth , dstHeight , bpp ) ;
2020-06-02 07:51:38 +00:00
RebindFramebuffer ( " RebindFramebuffer - Inter-buffer block transfer " ) ;
2019-02-08 13:50:47 +00:00
SetColorUpdated ( dstBuffer , skipDrawReason ) ;
return true ; // No need to actually do the memory copy behind, probably.
2014-09-13 22:40:55 +00:00
}
return false ;
} else if ( dstBuffer ) {
// Here we should just draw the pixels into the buffer. Copy first.
return false ;
} else if ( srcBuffer ) {
2020-08-16 20:38:15 +00:00
WARN_LOG_N_TIMES ( btd , 100 , G3D , " Block transfer readback %08x (x:%d y:%d stride:%d) -> %08x (x:%d y:%d stride:%d) (%dx%d %dbpp) " ,
2020-08-16 20:22:58 +00:00
srcBasePtr , srcX , srcY , srcStride ,
dstBasePtr , dstX , dstY , dstStride ,
width , height , bpp ) ;
2014-09-13 22:40:55 +00:00
FlushBeforeCopy ( ) ;
if ( g_Config . bBlockTransferGPU & & ! srcBuffer - > memoryUpdated ) {
const int srcBpp = srcBuffer - > format = = GE_FORMAT_8888 ? 4 : 2 ;
const float srcXFactor = ( float ) bpp / srcBpp ;
2015-02-28 09:28:53 +00:00
const bool tooTall = srcY + srcHeight > srcBuffer - > bufferHeight ;
if ( srcHeight < = 0 | | ( tooTall & & srcY ! = 0 ) ) {
2014-09-13 22:40:55 +00:00
WARN_LOG_ONCE ( btdheight , G3D , " Block transfer download %08x -> %08x skipped, %d+%d is taller than %d " , srcBasePtr , dstBasePtr , srcY , srcHeight , srcBuffer - > bufferHeight ) ;
} else {
2020-08-16 20:22:58 +00:00
if ( tooTall ) {
2015-02-28 09:28:53 +00:00
WARN_LOG_ONCE ( btdheight , G3D , " Block transfer download %08x -> %08x dangerous, %d+%d is taller than %d " , srcBasePtr , dstBasePtr , srcY , srcHeight , srcBuffer - > bufferHeight ) ;
2020-08-16 20:22:58 +00:00
}
2020-08-03 21:35:40 +00:00
ReadFramebufferToMemory ( srcBuffer , static_cast < int > ( srcX * srcXFactor ) , srcY , static_cast < int > ( srcWidth * srcXFactor ) , srcHeight ) ;
2017-04-09 22:10:07 +00:00
srcBuffer - > usageFlags = ( srcBuffer - > usageFlags | FB_USAGE_DOWNLOAD ) & ~ FB_USAGE_DOWNLOAD_CLEAR ;
2014-09-13 22:40:55 +00:00
}
}
return false ; // Let the bit copy happen
} else {
return false ;
}
}
2015-08-05 10:13:14 +00:00
void FramebufferManagerCommon : : NotifyBlockTransferAfter ( u32 dstBasePtr , int dstStride , int dstX , int dstY , u32 srcBasePtr , int srcStride , int srcX , int srcY , int width , int height , int bpp , u32 skipDrawReason ) {
2020-05-12 02:11:43 +00:00
// If it's a block transfer direct to the screen, and we're not using buffers, draw immediately.
// We may still do a partial block draw below if this doesn't pass.
if ( ! useBufferedRendering_ & & dstStride > = 480 & & width > = 480 & & height = = 272 ) {
bool isPrevDisplayBuffer = PrevDisplayFramebufAddr ( ) = = dstBasePtr ;
bool isDisplayBuffer = DisplayFramebufAddr ( ) = = dstBasePtr ;
if ( isPrevDisplayBuffer | | isDisplayBuffer ) {
FlushBeforeCopy ( ) ;
DrawFramebufferToOutput ( Memory : : GetPointerUnchecked ( dstBasePtr ) , displayFormat_ , dstStride ) ;
return ;
}
2014-09-13 22:40:55 +00:00
}
if ( MayIntersectFramebuffer ( srcBasePtr ) | | MayIntersectFramebuffer ( dstBasePtr ) ) {
VirtualFramebuffer * dstBuffer = 0 ;
VirtualFramebuffer * srcBuffer = 0 ;
int srcWidth = width ;
int srcHeight = height ;
int dstWidth = width ;
int dstHeight = height ;
FindTransferFramebuffers ( dstBuffer , srcBuffer , dstBasePtr , dstStride , dstX , dstY , srcBasePtr , srcStride , srcX , srcY , srcWidth , srcHeight , dstWidth , dstHeight , bpp ) ;
2020-05-12 02:11:43 +00:00
// A few games use this INSTEAD of actually drawing the video image to the screen, they just blast it to
// the backbuffer. Detect this and have the framebuffermanager draw the pixels.
2014-09-13 22:40:55 +00:00
if ( ! useBufferedRendering_ & & currentRenderVfb_ ! = dstBuffer ) {
return ;
}
if ( dstBuffer & & ! srcBuffer ) {
WARN_LOG_ONCE ( btu , G3D , " Block transfer upload %08x -> %08x " , srcBasePtr , dstBasePtr ) ;
2019-02-08 13:50:47 +00:00
FlushBeforeCopy ( ) ;
const u8 * srcBase = Memory : : GetPointerUnchecked ( srcBasePtr ) + ( srcX + srcY * srcStride ) * bpp ;
int dstBpp = dstBuffer - > format = = GE_FORMAT_8888 ? 4 : 2 ;
float dstXFactor = ( float ) bpp / dstBpp ;
if ( dstWidth > dstBuffer - > width | | dstHeight > dstBuffer - > height ) {
// The buffer isn't big enough, and we have a clear hint of size. Resize.
// This happens in Valkyrie Profile when uploading video at the ending.
ResizeFramebufFBO ( dstBuffer , dstWidth , dstHeight , false , true ) ;
// Make sure we don't flop back and forth.
dstBuffer - > newWidth = std : : max ( dstWidth , ( int ) dstBuffer - > width ) ;
dstBuffer - > newHeight = std : : max ( dstHeight , ( int ) dstBuffer - > height ) ;
dstBuffer - > lastFrameNewSize = gpuStats . numFlips ;
// Resizing may change the viewport/etc.
gstate_c . Dirty ( DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE ) ;
2014-09-13 22:40:55 +00:00
}
2019-02-08 13:50:47 +00:00
DrawPixels ( dstBuffer , static_cast < int > ( dstX * dstXFactor ) , dstY , srcBase , dstBuffer - > format , static_cast < int > ( srcStride * dstXFactor ) , static_cast < int > ( dstWidth * dstXFactor ) , dstHeight ) ;
SetColorUpdated ( dstBuffer , skipDrawReason ) ;
2020-06-02 07:51:38 +00:00
RebindFramebuffer ( " RebindFramebuffer - NotifyBlockTransferAfter " ) ;
2014-09-13 22:40:55 +00:00
}
}
}
2015-01-23 09:40:49 +00:00
void FramebufferManagerCommon : : SetRenderSize ( VirtualFramebuffer * vfb ) {
2015-09-19 14:19:03 +00:00
float renderWidthFactor = renderWidth_ / 480.0f ;
float renderHeightFactor = renderHeight_ / 272.0f ;
2015-01-23 09:40:49 +00:00
bool force1x = false ;
2017-04-14 06:35:07 +00:00
switch ( bloomHack_ ) {
2015-01-23 09:40:49 +00:00
case 1 :
force1x = vfb - > bufferWidth < = 128 | | vfb - > bufferHeight < = 64 ;
break ;
case 2 :
force1x = vfb - > bufferWidth < = 256 | | vfb - > bufferHeight < = 128 ;
break ;
case 3 :
2019-10-19 17:07:34 +00:00
force1x = vfb - > bufferWidth < 480 | | vfb - > bufferWidth > 800 | | vfb - > bufferHeight < 272 ; // GOW uses 864 x272
2015-01-23 09:40:49 +00:00
break ;
}
2019-06-02 18:48:46 +00:00
if ( PSP_CoreParameter ( ) . compat . flags ( ) . Force04154000Download & & vfb - > fb_address = = 0x04154000 ) {
2016-05-20 03:55:34 +00:00
force1x = true ;
}
2015-01-23 09:40:49 +00:00
if ( force1x & & g_Config . iInternalResolution ! = 1 ) {
vfb - > renderWidth = vfb - > bufferWidth ;
vfb - > renderHeight = vfb - > bufferHeight ;
2016-05-20 03:55:34 +00:00
} else {
2015-01-25 23:25:48 +00:00
vfb - > renderWidth = ( u16 ) ( vfb - > bufferWidth * renderWidthFactor ) ;
vfb - > renderHeight = ( u16 ) ( vfb - > bufferHeight * renderHeightFactor ) ;
2015-01-23 09:40:49 +00:00
}
2015-03-14 21:58:32 +00:00
}
2016-05-20 03:55:34 +00:00
void FramebufferManagerCommon : : SetSafeSize ( u16 w , u16 h ) {
VirtualFramebuffer * vfb = currentRenderVfb_ ;
if ( vfb ) {
2020-05-23 07:12:22 +00:00
vfb - > safeWidth = std : : min ( vfb - > bufferWidth , std : : max ( vfb - > safeWidth , w ) ) ;
vfb - > safeHeight = std : : min ( vfb - > bufferHeight , std : : max ( vfb - > safeHeight , h ) ) ;
2016-05-20 03:55:34 +00:00
}
}
2017-04-24 18:58:16 +00:00
void FramebufferManagerCommon : : Resized ( ) {
2017-04-24 18:59:12 +00:00
gstate_c . skipDrawReason & = ~ SKIPDRAW_NON_DISPLAYED_FB ;
2020-05-16 07:31:14 +00:00
int w , h ;
presentation_ - > CalculateRenderResolution ( & w , & h , & postShaderIsUpscalingFilter_ , & postShaderIsSupersampling_ ) ;
PSP_CoreParameter ( ) . renderWidth = w ;
PSP_CoreParameter ( ) . renderHeight = h ;
2020-05-13 07:06:13 +00:00
if ( UpdateSize ( ) ) {
DestroyAllFBOs ( ) ;
}
// Might have a new post shader - let's compile it.
presentation_ - > UpdatePostShader ( ) ;
2017-04-24 18:58:16 +00:00
# ifdef _WIN32
// Seems related - if you're ok with numbers all the time, show some more :)
if ( g_Config . iShowFPSCounter ! = 0 ) {
ShowScreenResolution ( ) ;
}
# endif
}
2020-05-13 07:06:13 +00:00
void FramebufferManagerCommon : : DestroyAllFBOs ( ) {
currentRenderVfb_ = nullptr ;
displayFramebuf_ = nullptr ;
prevDisplayFramebuf_ = nullptr ;
prevPrevDisplayFramebuf_ = nullptr ;
for ( VirtualFramebuffer * vfb : vfbs_ ) {
INFO_LOG ( FRAMEBUF , " Destroying FBO for %08x : %i x %i x %i " , vfb - > fb_address , vfb - > width , vfb - > height , vfb - > format ) ;
DestroyFramebuf ( vfb ) ;
}
vfbs_ . clear ( ) ;
for ( VirtualFramebuffer * vfb : bvfbs_ ) {
DestroyFramebuf ( vfb ) ;
}
bvfbs_ . clear ( ) ;
for ( auto & tempFB : tempFBOs_ ) {
tempFB . second . fbo - > Release ( ) ;
}
tempFBOs_ . clear ( ) ;
}
2020-08-27 14:51:39 +00:00
Draw : : Framebuffer * FramebufferManagerCommon : : GetTempFBO ( TempFBO reason , u16 w , u16 h , Draw : : FBColorDepth color_depth ) {
u64 key = ( ( u64 ) reason < < 48 ) | ( ( u64 ) color_depth < < 32 ) | ( ( u32 ) w < < 16 ) | h ;
2017-02-06 23:24:38 +00:00
auto it = tempFBOs_ . find ( key ) ;
if ( it ! = tempFBOs_ . end ( ) ) {
it - > second . last_frame_used = gpuStats . numFlips ;
return it - > second . fbo ;
}
2019-03-10 15:35:31 +00:00
bool z_stencil = reason = = TempFBO : : STENCIL ;
2020-08-27 14:51:39 +00:00
char name [ 128 ] ;
snprintf ( name , sizeof ( name ) , " temp_fbo_%dx%d%s " , w , h , z_stencil ? " _depth " : " " ) ;
Draw : : Framebuffer * fbo = draw_ - > CreateFramebuffer ( { w , h , 1 , 1 , z_stencil , color_depth , name } ) ;
if ( ! fbo ) {
return nullptr ;
}
2017-05-16 14:00:34 +00:00
2018-05-06 15:57:44 +00:00
const TempFBOInfo info = { fbo , gpuStats . numFlips } ;
2017-02-06 23:24:38 +00:00
tempFBOs_ [ key ] = info ;
return fbo ;
}
2017-02-06 23:19:31 +00:00
2015-03-14 21:58:32 +00:00
void FramebufferManagerCommon : : UpdateFramebufUsage ( VirtualFramebuffer * vfb ) {
auto checkFlag = [ & ] ( u16 flag , int last_frame ) {
if ( vfb - > usageFlags & flag ) {
const int age = frameLastFramebufUsed_ - last_frame ;
if ( age > FBO_OLD_USAGE_FLAG ) {
vfb - > usageFlags & = ~ flag ;
}
}
} ;
checkFlag ( FB_USAGE_DISPLAYED_FRAMEBUFFER , vfb - > last_frame_displayed ) ;
checkFlag ( FB_USAGE_TEXTURE , vfb - > last_frame_used ) ;
checkFlag ( FB_USAGE_RENDERTARGET , vfb - > last_frame_render ) ;
2015-04-28 07:08:00 +00:00
checkFlag ( FB_USAGE_CLUT , vfb - > last_frame_clut ) ;
2015-03-14 21:58:32 +00:00
}
2015-09-23 10:25:38 +00:00
void FramebufferManagerCommon : : ShowScreenResolution ( ) {
2020-01-26 18:43:18 +00:00
auto gr = GetI18NCategory ( " Graphics " ) ;
2015-09-23 10:25:38 +00:00
std : : ostringstream messageStream ;
messageStream < < gr - > T ( " Internal Resolution " ) < < " : " ;
messageStream < < PSP_CoreParameter ( ) . renderWidth < < " x " < < PSP_CoreParameter ( ) . renderHeight < < " " ;
2015-12-27 20:05:12 +00:00
if ( postShaderIsUpscalingFilter_ ) {
messageStream < < gr - > T ( " (upscaling) " ) < < " " ;
2020-05-16 07:31:14 +00:00
} else if ( postShaderIsSupersampling_ ) {
2018-04-01 15:00:10 +00:00
messageStream < < gr - > T ( " (supersampling) " ) < < " " ;
2015-12-27 20:05:12 +00:00
}
2015-09-23 10:25:38 +00:00
messageStream < < gr - > T ( " Window Size " ) < < " : " ;
messageStream < < PSP_CoreParameter ( ) . pixelWidth < < " x " < < PSP_CoreParameter ( ) . pixelHeight ;
2016-05-28 05:00:14 +00:00
host - > NotifyUserMessage ( messageStream . str ( ) , 2.0f , 0xFFFFFF , " resize " ) ;
2017-03-06 12:50:22 +00:00
INFO_LOG ( SYSTEM , " %s " , messageStream . str ( ) . c_str ( ) ) ;
2016-09-19 03:18:55 +00:00
}
2017-10-11 11:37:00 +00:00
2017-10-22 08:10:59 +00:00
// We might also want to implement an asynchronous callback-style version of this. Would probably
// only be possible to implement optimally on Vulkan, but on GL and D3D11 we could do pixel buffers
// and read on the next frame, then call the callback. PackFramebufferAsync_ on OpenGL already does something similar.
//
// The main use cases for this are:
// * GE debugging(in practice async will not matter because it will stall anyway.)
// * Video file recording(would probably be great if it was async.)
// * Screenshots(benefit slightly from async.)
// * Save state screenshots(could probably be async but need to manage the stall.)
2017-10-11 11:37:00 +00:00
bool FramebufferManagerCommon : : GetFramebuffer ( u32 fb_address , int fb_stride , GEBufferFormat format , GPUDebugBuffer & buffer , int maxRes ) {
VirtualFramebuffer * vfb = currentRenderVfb_ ;
if ( ! vfb ) {
vfb = GetVFBAt ( fb_address ) ;
}
if ( ! vfb ) {
// If there's no vfb and we're drawing there, must be memory?
2018-11-11 09:54:28 +00:00
buffer = GPUDebugBuffer ( Memory : : GetPointer ( fb_address ) , fb_stride , 512 , format ) ;
2017-10-11 11:37:00 +00:00
return true ;
}
int w = vfb - > renderWidth , h = vfb - > renderHeight ;
Draw : : Framebuffer * bound = nullptr ;
if ( vfb - > fbo ) {
if ( maxRes > 0 & & vfb - > renderWidth > vfb - > width * maxRes ) {
w = vfb - > width * maxRes ;
h = vfb - > height * maxRes ;
2018-05-06 15:57:44 +00:00
Draw : : Framebuffer * tempFBO = GetTempFBO ( TempFBO : : COPY , w , h ) ;
2017-10-11 11:37:00 +00:00
VirtualFramebuffer tempVfb = * vfb ;
tempVfb . fbo = tempFBO ;
tempVfb . bufferWidth = vfb - > width ;
tempVfb . bufferHeight = vfb - > height ;
tempVfb . renderWidth = w ;
tempVfb . renderHeight = h ;
BlitFramebuffer ( & tempVfb , 0 , 0 , vfb , 0 , 0 , vfb - > width , vfb - > height , 0 ) ;
bound = tempFBO ;
} else {
bound = vfb - > fbo ;
}
}
2017-10-18 09:40:07 +00:00
if ( ! useBufferedRendering_ ) {
// Safety check.
w = std : : min ( w , PSP_CoreParameter ( ) . pixelWidth ) ;
h = std : : min ( h , PSP_CoreParameter ( ) . pixelHeight ) ;
}
// TODO: Maybe should handle flipY inside CopyFramebufferToMemorySync somehow?
2017-12-26 23:55:24 +00:00
bool flipY = ( GetGPUBackend ( ) = = GPUBackend : : OPENGL & & ! useBufferedRendering_ ) ? true : false ;
2018-06-16 20:30:18 +00:00
buffer . Allocate ( w , h , GE_FORMAT_8888 , flipY ) ;
2020-05-21 09:24:05 +00:00
bool retval = draw_ - > CopyFramebufferToMemorySync ( bound , Draw : : FB_COLOR_BIT , 0 , 0 , w , h , Draw : : DataFormat : : R8G8B8A8_UNORM , buffer . GetData ( ) , w , " GetFramebuffer " ) ;
2017-11-08 10:57:53 +00:00
gpuStats . numReadbacks + + ;
2017-12-27 12:03:04 +00:00
// After a readback we'll have flushed and started over, need to dirty a bunch of things to be safe.
2020-05-24 18:57:59 +00:00
gstate_c . Dirty ( DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS ) ;
2017-10-11 11:37:00 +00:00
// We may have blitted to a temp FBO.
2020-06-02 07:51:38 +00:00
RebindFramebuffer ( " RebindFramebuffer - GetFramebuffer " ) ;
2017-10-11 11:37:00 +00:00
return retval ;
}
2017-10-11 13:21:53 +00:00
bool FramebufferManagerCommon : : GetDepthbuffer ( u32 fb_address , int fb_stride , u32 z_address , int z_stride , GPUDebugBuffer & buffer ) {
VirtualFramebuffer * vfb = currentRenderVfb_ ;
if ( ! vfb ) {
vfb = GetVFBAt ( fb_address ) ;
}
if ( ! vfb ) {
// If there's no vfb and we're drawing there, must be memory?
2018-11-11 09:54:28 +00:00
buffer = GPUDebugBuffer ( Memory : : GetPointer ( z_address ) , z_stride , 512 , GPU_DBG_FORMAT_16BIT ) ;
2017-10-11 13:21:53 +00:00
return true ;
}
2017-10-18 09:40:07 +00:00
int w = vfb - > renderWidth ;
int h = vfb - > renderHeight ;
if ( ! useBufferedRendering_ ) {
// Safety check.
w = std : : min ( w , PSP_CoreParameter ( ) . pixelWidth ) ;
h = std : : min ( h , PSP_CoreParameter ( ) . pixelHeight ) ;
2017-10-11 13:21:53 +00:00
}
2017-12-26 23:55:24 +00:00
bool flipY = ( GetGPUBackend ( ) = = GPUBackend : : OPENGL & & ! useBufferedRendering_ ) ? true : false ;
2017-10-11 13:21:53 +00:00
if ( gstate_c . Supports ( GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT ) ) {
2017-10-18 09:40:07 +00:00
buffer . Allocate ( w , h , GPU_DBG_FORMAT_FLOAT_DIV_256 , flipY ) ;
2017-10-11 13:21:53 +00:00
} else {
2017-10-18 09:40:07 +00:00
buffer . Allocate ( w , h , GPU_DBG_FORMAT_FLOAT , flipY ) ;
2017-10-11 13:21:53 +00:00
}
2017-12-22 20:29:08 +00:00
// No need to free on failure, that's the caller's job (it likely will reuse a buffer.)
2020-05-21 09:24:05 +00:00
bool retval = draw_ - > CopyFramebufferToMemorySync ( vfb - > fbo , Draw : : FB_DEPTH_BIT , 0 , 0 , w , h , Draw : : DataFormat : : D32F , buffer . GetData ( ) , w , " GetDepthBuffer " ) ;
2017-12-27 13:33:18 +00:00
// After a readback we'll have flushed and started over, need to dirty a bunch of things to be safe.
2020-05-24 18:57:59 +00:00
gstate_c . Dirty ( DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS ) ;
2017-12-22 20:29:08 +00:00
// That may have unbound the framebuffer, rebind to avoid crashes when debugging.
2020-06-02 07:51:38 +00:00
RebindFramebuffer ( " RebindFramebuffer - GetDepthbuffer " ) ;
2017-12-22 20:29:08 +00:00
return retval ;
2017-10-11 13:21:53 +00:00
}
bool FramebufferManagerCommon : : GetStencilbuffer ( u32 fb_address , int fb_stride , GPUDebugBuffer & buffer ) {
VirtualFramebuffer * vfb = currentRenderVfb_ ;
if ( ! vfb ) {
vfb = GetVFBAt ( fb_address ) ;
}
if ( ! vfb ) {
// If there's no vfb and we're drawing there, must be memory?
// TODO: Actually get the stencil.
2018-11-11 09:54:28 +00:00
buffer = GPUDebugBuffer ( Memory : : GetPointer ( fb_address ) , fb_stride , 512 , GPU_DBG_FORMAT_8888 ) ;
2017-10-11 13:21:53 +00:00
return true ;
}
2017-10-18 09:40:07 +00:00
int w = vfb - > renderWidth ;
int h = vfb - > renderHeight ;
if ( ! useBufferedRendering_ ) {
// Safety check.
w = std : : min ( w , PSP_CoreParameter ( ) . pixelWidth ) ;
h = std : : min ( h , PSP_CoreParameter ( ) . pixelHeight ) ;
}
2017-12-26 23:55:24 +00:00
bool flipY = ( GetGPUBackend ( ) = = GPUBackend : : OPENGL & & ! useBufferedRendering_ ) ? true : false ;
2017-12-22 20:29:08 +00:00
// No need to free on failure, the caller/destructor will do that. Usually this is a reused buffer, anyway.
2017-10-18 09:40:07 +00:00
buffer . Allocate ( w , h , GPU_DBG_FORMAT_8BIT , flipY ) ;
2020-05-21 09:24:05 +00:00
bool retval = draw_ - > CopyFramebufferToMemorySync ( vfb - > fbo , Draw : : FB_STENCIL_BIT , 0 , 0 , w , h , Draw : : DataFormat : : S8 , buffer . GetData ( ) , w , " GetStencilbuffer " ) ;
2017-12-22 20:29:08 +00:00
// That may have unbound the framebuffer, rebind to avoid crashes when debugging.
2020-06-02 07:51:38 +00:00
RebindFramebuffer ( " RebindFramebuffer - GetStencilbuffer " ) ;
2017-12-22 20:29:08 +00:00
return retval ;
2017-10-11 13:21:53 +00:00
}
2017-10-16 14:27:16 +00:00
bool FramebufferManagerCommon : : GetOutputFramebuffer ( GPUDebugBuffer & buffer ) {
int w , h ;
draw_ - > GetFramebufferDimensions ( nullptr , & w , & h ) ;
2018-06-16 20:47:51 +00:00
Draw : : DataFormat fmt = draw_ - > PreferredFramebufferReadbackFormat ( nullptr ) ;
// Ignore preferred formats other than BGRA.
if ( fmt ! = Draw : : DataFormat : : B8G8R8A8_UNORM )
fmt = Draw : : DataFormat : : R8G8B8A8_UNORM ;
buffer . Allocate ( w , h , fmt = = Draw : : DataFormat : : R8G8B8A8_UNORM ? GPU_DBG_FORMAT_8888 : GPU_DBG_FORMAT_8888_BGRA , false ) ;
2020-05-21 09:24:05 +00:00
bool retval = draw_ - > CopyFramebufferToMemorySync ( nullptr , Draw : : FB_COLOR_BIT , 0 , 0 , w , h , fmt , buffer . GetData ( ) , w , " GetOutputFramebuffer " ) ;
2017-12-22 20:29:08 +00:00
// That may have unbound the framebuffer, rebind to avoid crashes when debugging.
2020-06-02 07:51:38 +00:00
RebindFramebuffer ( " RebindFramebuffer - GetOutputFramebuffer " ) ;
2017-12-22 20:29:08 +00:00
return retval ;
2017-10-16 14:27:16 +00:00
}
2017-10-18 09:20:58 +00:00
// This function takes an already correctly-sized framebuffer and packs it into RAM.
// Does not need to account for scaling.
2017-10-22 08:10:59 +00:00
// Color conversion is currently done on CPU but should theoretically be done on GPU.
// (Except using the GPU might cause problems because of various implementations'
// dithering behavior and games that expect exact colors like Danganronpa, so we
// can't entirely be rid of the CPU path.) -- unknown
2017-10-18 09:20:58 +00:00
void FramebufferManagerCommon : : PackFramebufferSync_ ( VirtualFramebuffer * vfb , int x , int y , int w , int h ) {
if ( ! vfb - > fbo ) {
2017-10-22 08:10:59 +00:00
ERROR_LOG_REPORT_ONCE ( vfbfbozero , SCEGE , " PackFramebufferSync_: vfb->fbo == 0 " ) ;
2017-10-18 09:20:58 +00:00
return ;
}
2018-10-28 13:30:39 +00:00
if ( w < = 0 | | h < = 0 ) {
ERROR_LOG ( G3D , " Bad inputs to PackFramebufferSync_: %d %d %d %d " , x , y , w , h ) ;
return ;
}
2018-11-11 09:54:28 +00:00
const u32 fb_address = vfb - > fb_address & 0x3FFFFFFF ;
2017-10-18 09:20:58 +00:00
Draw : : DataFormat destFormat = GEFormatToThin3D ( vfb - > format ) ;
const int dstBpp = ( int ) DataFormatSizeInBytes ( destFormat ) ;
const int dstByteOffset = ( y * vfb - > fb_stride + x ) * dstBpp ;
2018-10-28 13:30:39 +00:00
2018-10-28 15:48:46 +00:00
if ( ! Memory : : IsValidRange ( fb_address + dstByteOffset , ( ( h - 1 ) * vfb - > fb_stride + w ) * dstBpp ) ) {
ERROR_LOG ( G3D , " PackFramebufferSync_ would write outside of memory, ignoring " ) ;
2018-10-28 13:30:39 +00:00
return ;
}
2017-10-18 09:20:58 +00:00
u8 * destPtr = Memory : : GetPointer ( fb_address + dstByteOffset ) ;
// We always need to convert from the framebuffer native format.
// Right now that's always 8888.
2018-10-28 13:30:39 +00:00
DEBUG_LOG ( G3D , " Reading framebuffer to mem, fb_address = %08x, ptr=%p " , fb_address , destPtr ) ;
2017-10-18 09:20:58 +00:00
2018-10-28 13:30:39 +00:00
if ( destPtr ) {
2020-05-21 09:24:05 +00:00
draw_ - > CopyFramebufferToMemorySync ( vfb - > fbo , Draw : : FB_COLOR_BIT , x , y , w , h , destFormat , destPtr , vfb - > fb_stride , " PackFramebufferSync_ " ) ;
2018-10-28 13:30:39 +00:00
} else {
ERROR_LOG ( G3D , " PackFramebufferSync_: Tried to readback to bad address %08x (stride = %d) " , fb_address + dstByteOffset , vfb - > fb_stride ) ;
}
2018-03-16 14:52:43 +00:00
2017-11-08 10:57:53 +00:00
gpuStats . numReadbacks + + ;
2017-10-18 09:20:58 +00:00
}
2020-08-03 21:35:40 +00:00
void FramebufferManagerCommon : : ReadFramebufferToMemory ( VirtualFramebuffer * vfb , int x , int y , int w , int h ) {
2017-11-05 09:01:03 +00:00
// Clamp to bufferWidth. Sometimes block transfers can cause this to hit.
if ( x + w > = vfb - > bufferWidth ) {
w = vfb - > bufferWidth - x ;
2017-11-01 13:43:00 +00:00
}
2017-11-30 23:37:43 +00:00
if ( vfb & & vfb - > fbo ) {
2017-10-18 09:20:58 +00:00
// We'll pseudo-blit framebuffers here to get a resized version of vfb.
OptimizeDownloadRange ( vfb , x , y , w , h ) ;
if ( vfb - > renderWidth = = vfb - > width & & vfb - > renderHeight = = vfb - > height ) {
// No need to blit
PackFramebufferSync_ ( vfb , x , y , w , h ) ;
} else {
VirtualFramebuffer * nvfb = FindDownloadTempBuffer ( vfb ) ;
2020-05-16 23:55:21 +00:00
if ( nvfb ) {
BlitFramebuffer ( nvfb , x , y , vfb , x , y , w , h , 0 ) ;
PackFramebufferSync_ ( nvfb , x , y , w , h ) ;
}
2017-10-18 09:20:58 +00:00
}
textureCache_ - > ForgetLastTexture ( ) ;
2020-06-02 07:51:38 +00:00
RebindFramebuffer ( " RebindFramebuffer - ReadFramebufferToMemory " ) ;
2017-10-18 09:20:58 +00:00
}
}
2017-10-18 10:26:02 +00:00
void FramebufferManagerCommon : : FlushBeforeCopy ( ) {
// Flush anything not yet drawn before blitting, downloading, or uploading.
// This might be a stalled list, or unflushed before a block transfer, etc.
// TODO: It's really bad that we are calling SetRenderFramebuffer here with
// all the irrelevant state checking it'll use to decide what to do. Should
// do something more focused here.
SetRenderFrameBuffer ( gstate_c . IsDirty ( DIRTY_FRAMEBUF ) , gstate_c . skipDrawReason ) ;
drawEngine_ - > DispatchFlush ( ) ;
}
2017-10-18 10:34:01 +00:00
void FramebufferManagerCommon : : DownloadFramebufferForClut ( u32 fb_address , u32 loadBytes ) {
VirtualFramebuffer * vfb = GetVFBAt ( fb_address ) ;
if ( vfb & & vfb - > fb_stride ! = 0 ) {
const u32 bpp = vfb - > drawnFormat = = GE_FORMAT_8888 ? 4 : 2 ;
int x = 0 ;
int y = 0 ;
int pixels = loadBytes / bpp ;
// The height will be 1 for each stride or part thereof.
int w = std : : min ( pixels % vfb - > fb_stride , ( int ) vfb - > width ) ;
int h = std : : min ( ( pixels + vfb - > fb_stride - 1 ) / vfb - > fb_stride , ( int ) vfb - > height ) ;
// We might still have a pending draw to the fb in question, flush if so.
FlushBeforeCopy ( ) ;
// No need to download if we already have it.
2017-11-06 22:49:09 +00:00
if ( w > 0 & & h > 0 & & ! vfb - > memoryUpdated & & vfb - > clutUpdatedBytes < loadBytes ) {
2017-10-18 10:34:01 +00:00
// We intentionally don't call OptimizeDownloadRange() here - we don't want to over download.
// CLUT framebuffers are often incorrectly estimated in size.
if ( x = = 0 & & y = = 0 & & w = = vfb - > width & & h = = vfb - > height ) {
vfb - > memoryUpdated = true ;
}
vfb - > clutUpdatedBytes = loadBytes ;
// We'll pseudo-blit framebuffers here to get a resized version of vfb.
VirtualFramebuffer * nvfb = FindDownloadTempBuffer ( vfb ) ;
2020-05-16 23:55:21 +00:00
if ( nvfb ) {
BlitFramebuffer ( nvfb , x , y , vfb , x , y , w , h , 0 ) ;
PackFramebufferSync_ ( nvfb , x , y , w , h ) ;
}
2017-10-18 10:34:01 +00:00
textureCache_ - > ForgetLastTexture ( ) ;
2020-06-02 07:51:38 +00:00
RebindFramebuffer ( " RebindFramebuffer - DownloadFramebufferForClut " ) ;
2017-10-18 10:34:01 +00:00
}
}
}
2017-10-18 10:49:15 +00:00
2020-06-02 07:51:38 +00:00
void FramebufferManagerCommon : : RebindFramebuffer ( const char * tag ) {
2018-07-28 09:09:01 +00:00
shaderManager_ - > DirtyLastShader ( ) ;
2017-11-15 19:44:25 +00:00
if ( currentRenderVfb_ & & currentRenderVfb_ - > fbo ) {
2020-06-02 07:51:38 +00:00
draw_ - > BindFramebufferAsRenderTarget ( currentRenderVfb_ - > fbo , { Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP } , tag ) ;
2017-11-15 19:44:25 +00:00
} else {
2017-12-22 20:29:08 +00:00
// Should this even happen? It could while debugging, but maybe we can just skip binding at all.
2020-05-21 09:24:05 +00:00
draw_ - > BindFramebufferAsRenderTarget ( nullptr , { Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP } , " RebindFramebuffer_Bad " ) ;
2017-11-15 19:44:25 +00:00
}
}
2017-10-18 10:49:15 +00:00
std : : vector < FramebufferInfo > FramebufferManagerCommon : : GetFramebufferList ( ) {
std : : vector < FramebufferInfo > list ;
for ( size_t i = 0 ; i < vfbs_ . size ( ) ; + + i ) {
VirtualFramebuffer * vfb = vfbs_ [ i ] ;
FramebufferInfo info ;
info . fb_address = vfb - > fb_address ;
info . z_address = vfb - > z_address ;
info . format = vfb - > format ;
info . width = vfb - > width ;
info . height = vfb - > height ;
info . fbo = vfb - > fbo ;
list . push_back ( info ) ;
}
return list ;
}