2014-09-09 08:12:42 -07:00
// Copyright (c) 2012- PPSSPP Project.
2020-05-24 20:27:58 +02:00
// This program is free software: you can redistribute it and/or modify
2014-09-09 08:12:42 -07: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 12:25:38 +02:00
# include <sstream>
2017-11-05 10:11:00 +01:00
# include <cmath>
2015-09-23 12:25:38 +02:00
2020-10-04 23:24:14 +02:00
# include "Common/GPU/thin3d.h"
# include "Common/GPU/OpenGL/GLFeatures.h"
2022-08-31 14:23:56 +02:00
# include "Common/Data/Collections/TinySet.h"
2021-05-01 07:15:04 -07:00
# include "Common/Data/Convert/ColorConv.h"
2020-10-01 13:05:04 +02:00
# include "Common/Data/Text/I18n.h"
2022-12-27 14:58:20 -08:00
# include "Common/LogReporting.h"
2022-08-03 12:14:28 +02:00
# include "Common/Math/lin/matrix4x4.h"
# include "Common/Math/math_util.h"
# include "Common/System/Display.h"
2022-11-22 21:49:52 +01:00
# include "Common/VR/PPSSPPVR.h"
2022-01-30 15:49:02 -08:00
# include "Common/CommonTypes.h"
2022-08-25 23:08:38 +02:00
# include "Common/StringUtils.h"
2014-09-09 22:56:54 -07:00
# include "Core/Config.h"
2018-06-16 18:42:31 -07:00
# include "Core/ConfigValues.h"
2020-07-04 20:57:05 +02:00
# include "Core/Core.h"
2014-09-09 22:56:54 -07:00
# include "Core/CoreParameter.h"
2021-02-02 00:08:05 -08:00
# include "Core/Debugger/MemBlockInfo.h"
2016-05-27 22:00:14 -07:00
# include "Core/Host.h"
2021-01-31 00:22:49 -08:00
# include "Core/MIPS/MIPS.h"
2017-10-18 12:26:02 +02:00
# include "GPU/Common/DrawEngineCommon.h"
2020-08-03 23:17:22 +02:00
# include "GPU/Common/FramebufferManagerCommon.h"
2017-04-24 11:58:16 -07:00
# include "GPU/Common/PostShader.h"
2020-05-09 22:06:22 -07:00
# include "GPU/Common/PresentationCommon.h"
2017-02-06 12:02:30 +01:00
# include "GPU/Common/TextureCacheCommon.h"
2020-11-03 15:44:57 +01:00
# include "GPU/Common/ReinterpretFramebuffer.h"
2022-08-23 19:29:06 -07:00
# include "GPU/Debugger/Debugger.h"
2018-11-17 09:21:51 -08:00
# include "GPU/Debugger/Record.h"
2020-11-07 11:10:54 +01:00
# include "GPU/Debugger/Stepping.h"
2014-09-09 22:56:54 -07:00
# include "GPU/GPUInterface.h"
2014-09-09 08:12:42 -07:00
# include "GPU/GPUState.h"
2022-12-10 21:02:44 -08:00
static size_t FormatFramebufferName ( const VirtualFramebuffer * vfb , char * tag , size_t len ) {
2022-09-28 14:09:40 +02:00
return snprintf ( tag , len , " FB_%08x_%08x_%dx%d_%s " , vfb - > fb_address , vfb - > z_address , vfb - > bufferWidth , vfb - > bufferHeight , GeBufferFormatToString ( vfb - > fb_format ) ) ;
}
2017-06-02 17:03:29 +02:00
FramebufferManagerCommon : : FramebufferManagerCommon ( Draw : : DrawContext * draw )
2022-08-23 10:35:58 +02:00
: draw_ ( draw ) , draw2D_ ( draw_ ) {
2020-05-09 22:06:22 -07:00
presentation_ = new PresentationCommon ( draw ) ;
2014-09-09 08:12:42 -07:00
}
FramebufferManagerCommon : : ~ FramebufferManagerCommon ( ) {
2021-03-11 23:28:02 +01:00
DeviceLost ( ) ;
2017-11-01 21:42:19 +01: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 18:53:52 +01:00
2020-05-09 22:06:22 -07:00
delete presentation_ ;
2023-02-03 20:53:49 +01:00
delete [ ] convBuf_ ;
2014-09-09 08:12:42 -07:00
}
2022-12-14 15:15:31 +01:00
void FramebufferManagerCommon : : Init ( int msaaLevel ) {
2020-05-16 09:57:01 -07:00
// We may need to override the render size if the shader is upscaling or SSAA.
2022-11-21 14:54:48 +01:00
NotifyDisplayResized ( ) ;
2022-12-14 15:15:31 +01:00
NotifyRenderResized ( msaaLevel ) ;
2014-09-13 16:47:23 -07:00
}
2022-12-14 15:15:31 +01:00
bool FramebufferManagerCommon : : UpdateRenderSize ( int msaaLevel ) {
const bool newRender = renderWidth_ ! = ( float ) PSP_CoreParameter ( ) . renderWidth | | renderHeight_ ! = ( float ) PSP_CoreParameter ( ) . renderHeight | | msaaLevel_ ! = msaaLevel ;
2022-09-04 23:42:35 +02:00
2022-12-02 00:35:28 +01:00
int effectiveBloomHack = g_Config . iBloomHack ;
if ( PSP_CoreParameter ( ) . compat . flags ( ) . ForceLowerResolutionForEffectsOn ) {
effectiveBloomHack = 3 ;
} else if ( PSP_CoreParameter ( ) . compat . flags ( ) . ForceLowerResolutionForEffectsOff ) {
effectiveBloomHack = 0 ;
}
2022-09-04 23:42:35 +02:00
2022-11-06 19:20:10 +01:00
bool newBuffered = ! g_Config . bSkipBufferEffects ;
const bool newSettings = bloomHack_ ! = effectiveBloomHack | | useBufferedRendering_ ! = newBuffered ;
2017-04-13 23:35:07 -07:00
2015-09-27 19:59:47 +02:00
renderWidth_ = ( float ) PSP_CoreParameter ( ) . renderWidth ;
renderHeight_ = ( float ) PSP_CoreParameter ( ) . renderHeight ;
2020-11-05 11:21:00 +01:00
renderScaleFactor_ = ( float ) PSP_CoreParameter ( ) . renderScaleFactor ;
2022-12-14 15:15:31 +01:00
msaaLevel_ = msaaLevel ;
2022-11-21 14:34:06 +01:00
2022-09-04 23:42:35 +02:00
bloomHack_ = effectiveBloomHack ;
2022-11-06 19:20:10 +01:00
useBufferedRendering_ = newBuffered ;
2017-04-13 23:35:07 -07:00
2022-11-21 14:34:06 +01:00
presentation_ - > UpdateRenderSize ( renderWidth_ , renderHeight_ ) ;
2017-04-13 23:35:07 -07:00
return newRender | | newSettings ;
2015-09-27 19:59:47 +02:00
}
2022-11-22 23:29:50 +01:00
void FramebufferManagerCommon : : CheckPostShaders ( ) {
2022-10-21 12:52:21 +02:00
if ( updatePostShaders_ ) {
presentation_ - > UpdatePostShader ( ) ;
updatePostShaders_ = false ;
}
2022-11-22 23:29:50 +01:00
}
void FramebufferManagerCommon : : BeginFrame ( ) {
DecimateFBOs ( ) ;
2022-10-21 12:52:21 +02:00
2017-10-25 20:28:12 +02:00
currentRenderVfb_ = nullptr ;
2014-09-09 22:56:54 -07:00
}
2014-09-09 08:12:42 -07:00
void FramebufferManagerCommon : : SetDisplayFramebuffer ( u32 framebuf , u32 stride , GEBufferFormat format ) {
2022-10-02 21:28:53 -07:00
displayFramebufPtr_ = framebuf & 0x3FFFFFFF ;
if ( Memory : : IsVRAMAddress ( displayFramebufPtr_ ) )
displayFramebufPtr_ = framebuf & 0x041FFFFF ;
2014-09-09 08:12:42 -07:00
displayStride_ = stride ;
displayFormat_ = format ;
2022-08-23 19:29:06 -07:00
GPUDebug : : NotifyDisplay ( framebuf , stride , format ) ;
2018-11-17 09:21:51 -08:00
GPURecord : : NotifyDisplay ( framebuf , stride , format ) ;
2014-09-09 08:12:42 -07:00
}
2022-08-16 10:55:44 +02:00
VirtualFramebuffer * FramebufferManagerCommon : : GetVFBAt ( u32 addr ) const {
2018-11-12 07:48:30 +01:00
addr & = 0x3FFFFFFF ;
2022-10-02 21:28:53 -07:00
if ( Memory : : IsVRAMAddress ( addr ) )
addr & = 0x041FFFFF ;
2016-01-04 22:23:37 -08:00
VirtualFramebuffer * match = nullptr ;
2022-08-25 00:52:45 +02:00
for ( auto vfb : vfbs_ ) {
if ( vfb - > fb_address = = addr ) {
2022-08-17 10:15:02 +02:00
// Could check w too but whatever (actually, might very well make sense to do so, depending on context).
2022-08-25 00:52:45 +02:00
if ( ! match | | vfb - > last_frame_render > match - > last_frame_render ) {
match = vfb ;
2014-09-09 22:09:41 -07:00
}
}
}
2016-01-04 22:23:37 -08:00
return match ;
2014-09-09 22:09:41 -07:00
}
2022-08-29 10:14:29 +02:00
VirtualFramebuffer * FramebufferManagerCommon : : GetExactVFB ( u32 addr , int stride , GEBufferFormat format ) const {
2022-10-02 21:28:53 -07:00
addr & = 0x3FFFFFFF ;
if ( Memory : : IsVRAMAddress ( addr ) )
addr & = 0x041FFFFF ;
2022-08-30 06:41:37 +02:00
VirtualFramebuffer * newest = nullptr ;
2022-08-29 10:14:29 +02:00
for ( auto vfb : vfbs_ ) {
if ( vfb - > fb_address = = addr & & vfb - > fb_stride = = stride & & vfb - > fb_format = = format ) {
2022-08-30 06:41:37 +02:00
if ( newest ) {
if ( vfb - > colorBindSeq > newest - > colorBindSeq ) {
newest = vfb ;
}
} else {
newest = vfb ;
}
2022-08-29 10:14:29 +02:00
}
}
2022-08-30 06:41:37 +02:00
return newest ;
2022-08-29 10:14:29 +02:00
}
VirtualFramebuffer * FramebufferManagerCommon : : ResolveVFB ( u32 addr , int stride , GEBufferFormat format ) {
2022-10-02 21:28:53 -07:00
addr & = 0x3FFFFFFF ;
if ( Memory : : IsVRAMAddress ( addr ) )
addr & = 0x041FFFFF ;
2022-08-29 10:14:29 +02:00
// Find the newest one matching addr and stride.
VirtualFramebuffer * newest = nullptr ;
for ( auto vfb : vfbs_ ) {
if ( vfb - > fb_address = = addr & & vfb - > FbStrideInBytes ( ) = = stride * BufferFormatBytesPerPixel ( format ) ) {
if ( newest ) {
if ( vfb - > colorBindSeq > newest - > colorBindSeq ) {
newest = vfb ;
}
} else {
newest = vfb ;
}
}
}
if ( newest & & newest - > fb_format ! = format ) {
WARN_LOG_ONCE ( resolvevfb , G3D , " ResolveVFB: Resolving from %s to %s at %08x/%d " , GeBufferFormatToString ( newest - > fb_format ) , GeBufferFormatToString ( format ) , addr , stride ) ;
return ResolveFramebufferColorToFormat ( newest , format ) ;
}
return newest ;
}
VirtualFramebuffer * FramebufferManagerCommon : : GetDisplayVFB ( ) {
return GetExactVFB ( displayFramebufPtr_ , displayStride_ , displayFormat_ ) ;
}
2019-09-17 14:45:40 +02:00
u32 FramebufferManagerCommon : : ColorBufferByteSize ( const VirtualFramebuffer * vfb ) const {
2022-08-22 21:28:43 +02:00
return vfb - > fb_stride * vfb - > height * ( vfb - > fb_format = = GE_FORMAT_8888 ? 4 : 2 ) ;
2014-09-09 22:56:54 -07:00
}
2014-09-09 08:12:42 -07:00
// Heuristics to figure out the size of FBO to create.
2019-09-17 14:45:40 +02:00
// TODO: Possibly differentiate on whether through mode is used (since in through mode, viewport is meaningless?)
2022-08-24 14:57:55 +02:00
void FramebufferManagerCommon : : EstimateDrawingSize ( u32 fb_address , int fb_stride , GEBufferFormat fb_format , int viewport_width , int viewport_height , int region_width , int region_height , int scissor_width , int scissor_height , int & drawing_width , int & drawing_height ) {
2014-09-09 08:12:42 -07: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 11:41:44 +01:00
if ( viewport_width > 4 & & viewport_width < = fb_stride & & viewport_height > 0 ) {
2014-09-09 08:12:42 -07: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 00:02:05 -08: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 08:12:42 -07: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 11:37:36 -07:00
if ( scissor_width = = 481 & & region_width = = 480 & & scissor_height = = 273 & & region_height = = 272 ) {
drawing_width = 480 ;
drawing_height = 272 ;
}
2014-09-09 08:12:42 -07: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 ;
2022-08-25 00:52:45 +02:00
for ( auto vfb : vfbs_ ) {
2022-10-02 21:28:53 -07:00
const u32 other_address = vfb - > fb_address ;
2018-11-12 07:52:58 +01:00
if ( other_address > fb_address & & other_address < nearest_address ) {
2014-09-09 08:12:42 -07: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...
2022-08-18 10:51:50 +02:00
const u32 bpp = BufferFormatBytesPerPixel ( fb_format ) ;
2018-11-12 07:52:58 +01:00
int avail_height = ( nearest_address - fb_address ) / ( fb_stride * bpp ) ;
2014-09-09 08:12:42 -07: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 ;
}
}
2022-08-24 14:57:55 +02:00
bool margin = false ;
// Let's check if we're in a stride gap of a full-size framebuffer.
for ( auto vfb : vfbs_ ) {
if ( fb_address = = vfb - > fb_address ) {
continue ;
}
if ( vfb - > fb_stride ! = 512 ) {
continue ;
}
int vfb_stride_in_bytes = BufferFormatBytesPerPixel ( vfb - > fb_format ) * vfb - > fb_stride ;
int stride_in_bytes = BufferFormatBytesPerPixel ( fb_format ) * fb_stride ;
if ( stride_in_bytes ! = vfb_stride_in_bytes ) {
// Mismatching stride in bytes, not interesting
continue ;
}
if ( fb_address > vfb - > fb_address & & fb_address < vfb - > fb_address + vfb_stride_in_bytes ) {
// Candidate!
if ( vfb - > height = = drawing_height ) {
2022-08-24 18:35:42 +02:00
// Might have a margin texture! Fix the drawing width if it's too large.
2022-08-24 14:57:55 +02:00
int width_in_bytes = vfb - > fb_address + vfb_stride_in_bytes - fb_address ;
int width_in_pixels = width_in_bytes / BufferFormatBytesPerPixel ( fb_format ) ;
2022-08-24 18:35:42 +02:00
// Final check
if ( width_in_pixels < = 32 ) {
drawing_width = std : : min ( drawing_width , width_in_pixels ) ;
margin = true ;
// Don't really need to keep looking.
break ;
}
2022-08-24 14:57:55 +02:00
}
}
}
DEBUG_LOG ( G3D , " Est: %08x V: %ix%i, R: %ix%i, S: %ix%i, STR: %i, THR:%i, Z:%08x = %ix%i %s " , 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 , margin ? " (margin!) " : " " ) ;
2014-09-09 08:12:42 -07:00
}
2014-09-09 22:56:54 -07:00
2015-08-05 11:51:24 +02:00
void GetFramebufferHeuristicInputs ( FramebufferHeuristicParams * params , const GPUgstate & gstate ) {
2022-10-02 21:28:53 -07:00
// GetFramebufferHeuristicInputs is only called from rendering, and thus, it's VRAM.
params - > fb_address = gstate . getFrameBufRawAddress ( ) | 0x04000000 ;
2015-08-05 11:51:24 +02:00
params - > fb_stride = gstate . FrameBufStride ( ) ;
2014-09-09 22:56:54 -07:00
2022-10-02 21:28:53 -07:00
params - > z_address = gstate . getDepthBufRawAddress ( ) | 0x04000000 ;
2015-08-05 11:51:24 +02:00
params - > z_stride = gstate . DepthBufStride ( ) ;
2014-09-09 22:56:54 -07:00
2020-09-21 09:56:52 +02: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 ;
}
2022-08-22 21:22:00 +02:00
params - > fb_format = gstate_c . framebufFormat ;
2015-08-05 02:43:40 +02:00
2015-08-05 11:51:24 +02:00
params - > isClearingDepth = gstate . isModeClear ( ) & & gstate . isClearModeDepthMask ( ) ;
2015-08-05 02:43:40 +02: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 11:51:24 +02:00
params - > isWritingDepth = ! gstate . isClearModeDepthMask ( ) & & gstate . isDepthWriteEnabled ( ) ;
2015-08-05 02:43:40 +02:00
} else {
2015-08-05 11:51:24 +02:00
params - > isWritingDepth = gstate . isDepthWriteEnabled ( ) ;
2015-08-05 02:43:40 +02:00
}
2015-08-05 11:51:24 +02:00
params - > isDrawing = ! gstate . isModeClear ( ) | | ! gstate . isClearModeColorMask ( ) | | ! gstate . isClearModeAlphaMask ( ) ;
params - > isModeThrough = gstate . isModeThrough ( ) ;
2022-08-14 22:13:39 -07:00
const bool alphaBlending = gstate . isAlphaBlendEnabled ( ) ;
const bool logicOpBlending = gstate . isLogicOpEnabled ( ) & & gstate . getLogicOp ( ) ! = GE_LOGIC_CLEAR & & gstate . getLogicOp ( ) ! = GE_LOGIC_COPY ;
params - > isBlending = alphaBlending | | logicOpBlending ;
2014-09-09 22:56:54 -07:00
2015-08-05 02:43:40 +02:00
// Viewport-X1 and Y1 are not the upper left corner, but half the width/height. A bit confusing.
2017-11-04 11:41:44 +01: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 13:44:23 +01:00
if ( std : : isnan ( vpx ) | | vpx > 10000000.0f ) {
2017-11-04 11:41:44 +01:00
vpx = 0.f ;
}
2017-11-05 13:44:23 +01:00
if ( std : : isnan ( vpy ) | | vpy > 10000000.0f ) {
2017-11-04 11:41:44 +01:00
vpy = 0.f ;
}
params - > viewportWidth = ( int ) ( fabsf ( vpx ) * 2.0f ) ;
params - > viewportHeight = ( int ) ( fabsf ( vpy ) * 2.0f ) ;
2015-08-05 11:51:24 +02:00
params - > regionWidth = gstate . getRegionX2 ( ) + 1 ;
params - > regionHeight = gstate . getRegionY2 ( ) + 1 ;
2022-08-31 11:40:10 +02:00
params - > scissorLeft = gstate . getScissorX1 ( ) ;
params - > scissorTop = gstate . getScissorY1 ( ) ;
params - > scissorRight = gstate . getScissorX2 ( ) + 1 ;
params - > scissorBottom = gstate . getScissorY2 ( ) + 1 ;
2022-01-23 19:36:37 -08:00
if ( gstate . getRegionRateX ( ) ! = 0x100 | | gstate . getRegionRateY ( ) ! = 0x100 ) {
WARN_LOG_REPORT_ONCE ( regionRate , G3D , " Drawing region rate add non-zero: %04x, %04x of %04x, %04x " , gstate . getRegionRateX ( ) , gstate . getRegionRateY ( ) , gstate . getRegionX2 ( ) , gstate . getRegionY2 ( ) ) ;
}
2015-08-05 11:51:24 +02:00
}
2022-08-31 09:12:52 +02:00
static void ApplyKillzoneFramebufferSplit ( FramebufferHeuristicParams * params , int * drawing_width ) ;
2022-08-30 19:36:08 +02:00
VirtualFramebuffer * FramebufferManagerCommon : : DoSetRenderFrameBuffer ( FramebufferHeuristicParams & params , u32 skipDrawReason ) {
2017-01-23 23:25:09 +01:00
gstate_c . Clean ( DIRTY_FRAMEBUF ) ;
2015-08-05 11:51:24 +02: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-05 01:03:49 +02:00
2014-09-09 22:56:54 -07: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 ;
2022-08-31 11:40:10 +02:00
EstimateDrawingSize ( params . fb_address , std : : max ( params . fb_stride , ( u16 ) 4 ) , params . fb_format , params . viewportWidth , params . viewportHeight , params . regionWidth , params . regionHeight , params . scissorRight , params . scissorBottom , drawing_width , drawing_height ) ;
2014-09-09 22:56:54 -07:00
2022-07-31 12:02:00 +02:00
if ( params . fb_address = = params . z_address ) {
// Most likely Z will not be used in this pass, as that would wreak havoc (undefined behavior for sure)
// We probably don't need to do anything about that, but let's log it.
WARN_LOG_ONCE ( color_equal_z , G3D , " Framebuffer bound with color addr == z addr, likely will not use Z in this pass: %08x " , params . fb_address ) ;
}
2022-08-31 01:53:05 +02:00
// Compatibility hack for Killzone, see issue #6207.
2022-08-30 19:36:08 +02:00
if ( PSP_CoreParameter ( ) . compat . flags ( ) . SplitFramebufferMargin & & params . fb_format = = GE_FORMAT_8888 ) {
2022-08-31 09:12:52 +02:00
ApplyKillzoneFramebufferSplit ( & params , & drawing_width ) ;
2022-08-31 01:09:23 +02:00
} else {
gstate_c . SetCurRTOffset ( 0 , 0 ) ;
2022-08-30 19:36:08 +02:00
}
2022-08-29 10:14:29 +02:00
// Find a matching framebuffer.
2017-10-25 21:19:42 +02:00
VirtualFramebuffer * vfb = nullptr ;
2022-08-25 00:52:45 +02:00
for ( auto v : vfbs_ ) {
2022-08-22 21:28:43 +02:00
const u32 bpp = BufferFormatBytesPerPixel ( v - > fb_format ) ;
2022-07-24 11:50:06 +02:00
2022-08-22 23:30:28 +02:00
if ( params . fb_address = = v - > fb_address & & params . fb_format = = v - > fb_format & & params . fb_stride = = v - > fb_stride ) {
2014-09-09 22:56:54 -07:00
vfb = v ;
2022-08-22 23:30:28 +02:00
2020-09-21 09:56:52 +02:00
if ( vfb - > z_address = = 0 & & vfb - > z_stride = = 0 & & params . z_stride ! = 0 ) {
2018-11-11 23:13:53 +01: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 12:57:37 -08:00
// Keep track, but this isn't really used.
vfb - > z_stride = params . z_stride ;
2015-08-05 02:43:40 +02:00
// Heuristic: In throughmode, a higher height could be used. Let's avoid shrinking the buffer.
2017-04-12 00:20:50 -07:00
if ( params . isModeThrough & & ( int ) vfb - > width < = params . fb_stride ) {
2014-09-09 22:56:54 -07: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 ;
2022-12-07 13:23:33 +01:00
} else if ( ! PSP_CoreParameter ( ) . compat . flags ( ) . DisallowFramebufferAtOffset & & v - > fb_stride = = params . fb_stride & & v - > fb_format = = params . fb_format & & ! PSP_CoreParameter ( ) . compat . flags ( ) . SplitFramebufferMargin ) {
2022-08-18 09:38:17 +02:00
u32 v_fb_first_line_end_ptr = v - > fb_address + v - > fb_stride * bpp ;
2022-07-24 11:50:06 +02:00
u32 v_fb_end_ptr = v - > fb_address + v - > fb_stride * v - > height * bpp ;
if ( params . fb_address > v - > fb_address & & params . fb_address < v_fb_first_line_end_ptr ) {
const int x_offset = ( params . fb_address - v - > fb_address ) / bpp ;
if ( x_offset < params . fb_stride & & v - > height > = drawing_height ) {
// Pretty certainly a pure render-to-X-offset.
2022-08-27 13:57:36 +02:00
WARN_LOG_REPORT_ONCE ( renderoffset , HLE , " Rendering to framebuffer offset at %08x +%dx%d (stride %d) " , v - > fb_address , x_offset , 0 , v - > fb_stride ) ;
2022-07-24 11:50:06 +02:00
vfb = v ;
gstate_c . SetCurRTOffset ( x_offset , 0 ) ;
vfb - > width = std : : max ( ( int ) vfb - > width , x_offset + drawing_width ) ;
// To prevent the newSize code from being confused.
drawing_width + = x_offset ;
break ;
}
} else {
// We ignore this match.
// TODO: We can allow X/Y overlaps too, but haven't seen any so safer to not.
2014-09-09 22:56:54 -07:00
}
}
}
if ( vfb ) {
2022-12-12 23:23:14 -08:00
bool resized = false ;
2014-09-09 22:56:54 -07:00
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 ) ;
2022-12-12 23:23:14 -08:00
resized = true ;
2014-09-09 22:56:54 -07:00
} 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 11:51:24 +02:00
bool needsRecreate = vfb - > bufferWidth > params . fb_stride ;
2014-09-09 22:56:54 -07:00
needsRecreate = needsRecreate | | vfb - > newWidth > vfb - > bufferWidth | | vfb - > newWidth * 2 < vfb - > bufferWidth ;
2014-09-10 19:50:31 -04:00
needsRecreate = needsRecreate | | vfb - > newHeight > vfb - > bufferHeight | | vfb - > newHeight * 2 < vfb - > bufferHeight ;
2014-09-09 22:56:54 -07:00
if ( needsRecreate ) {
ResizeFramebufFBO ( vfb , vfb - > width , vfb - > height , true ) ;
2022-12-12 23:23:14 -08:00
resized = true ;
2016-05-19 20:55:34 -07:00
// Let's discard this information, might be wrong now.
vfb - > safeWidth = 0 ;
vfb - > safeHeight = 0 ;
2015-09-23 23:57:59 -07: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-09 22:56:54 -07:00
}
}
} else {
// It's not different, let's keep track of that too.
vfb - > lastFrameNewSize = gpuStats . numFlips ;
}
2022-12-12 23:23:14 -08:00
if ( ! resized & & renderScaleFactor_ ! = 1 & & vfb - > renderScaleFactor = = 1 ) {
// Might be time to change this framebuffer - have we used depth?
2023-02-08 18:45:01 -08:00
if ( ( vfb - > usageFlags & FB_USAGE_COLOR_MIXED_DEPTH ) & & ! PSP_CoreParameter ( ) . compat . flags ( ) . ForceLowerResolutionForEffectsOn ) {
2022-12-12 23:23:14 -08:00
ResizeFramebufFBO ( vfb , vfb - > width , vfb - > height , true ) ;
_assert_ ( vfb - > renderScaleFactor ! = 1 ) ;
}
}
2014-09-09 22:56:54 -07:00
}
// None found? Create one.
if ( ! vfb ) {
2022-08-01 23:55:58 +02:00
gstate_c . usingDepth = false ; // reset depth buffer tracking
2018-11-05 00:28:01 +01:00
vfb = new VirtualFramebuffer { } ;
2016-05-19 20:55:34 -07:00
vfb - > fbo = nullptr ;
2015-08-05 11:51:24 +02: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 ;
2020-11-05 11:21:00 +01:00
// The other width/height parameters are set in ResizeFramebufFBO below.
2014-09-09 22:56:54 -07:00
vfb - > width = drawing_width ;
vfb - > height = drawing_height ;
vfb - > newWidth = drawing_width ;
vfb - > newHeight = drawing_height ;
vfb - > lastFrameNewSize = gpuStats . numFlips ;
2022-08-22 21:28:43 +02:00
vfb - > fb_format = params . fb_format ;
2022-08-17 19:55:19 +02:00
vfb - > usageFlags = FB_USAGE_RENDER_COLOR ;
2014-09-09 22:56:54 -07:00
2022-09-12 15:34:32 +02:00
u32 colorByteSize = ColorBufferByteSize ( vfb ) ;
if ( Memory : : IsVRAMAddress ( params . fb_address ) & & params . fb_address + colorByteSize > framebufRangeEnd_ ) {
framebufRangeEnd_ = params . fb_address + colorByteSize ;
2014-09-09 22:56:54 -07:00
}
2020-11-05 11:21:00 +01:00
// This is where we actually create the framebuffer. The true is "force".
2014-09-09 22:56:54 -07:00
ResizeFramebufFBO ( vfb , drawing_width , drawing_height , true ) ;
NotifyRenderFramebufferCreated ( vfb ) ;
2022-08-18 10:51:50 +02:00
// Note that we do not even think about depth right now. That'll be handled
// on the first depth access, which will call SetDepthFramebuffer.
2022-08-17 12:11:00 +02:00
2022-08-18 10:51:50 +02:00
CopyToColorFromOverlappingFramebuffers ( vfb ) ;
2020-11-05 11:21:00 +01:00
SetColorUpdated ( vfb , skipDrawReason ) ;
2022-08-22 21:28:43 +02:00
INFO_LOG ( FRAMEBUF , " Creating FBO for %08x (z: %08x) : %d x %d x %s " , vfb - > fb_address , vfb - > z_address , vfb - > width , vfb - > height , GeBufferFormatToString ( vfb - > fb_format ) ) ;
2014-09-09 22:56:54 -07:00
vfb - > last_frame_render = gpuStats . numFlips ;
frameLastFramebufUsed_ = gpuStats . numFlips ;
vfbs_ . push_back ( vfb ) ;
currentRenderVfb_ = vfb ;
2022-08-14 13:19:52 -07:00
// Assume that if we're clearing right when switching to a new framebuffer, we don't need to upload.
2022-08-31 13:48:53 +02:00
if ( useBufferedRendering_ & & params . isDrawing ) {
2022-10-09 13:49:41 -07:00
gpu - > PerformWriteColorFromMemory ( params . fb_address , colorByteSize ) ;
// Alpha was already done by PerformWriteColorFromMemory.
PerformWriteStencilFromMemory ( params . fb_address , colorByteSize , WriteStencil : : STENCIL_IS_ZERO | WriteStencil : : IGNORE_ALPHA ) ;
2021-10-17 21:53:23 -07:00
// TODO: Is it worth trying to upload the depth buffer (only if it wasn't copied above..?)
2014-09-10 23:58:07 -07:00
}
2022-08-17 12:11:00 +02:00
// We already have it!
2014-09-09 22:56:54 -07:00
} else if ( vfb ! = currentRenderVfb_ ) {
// Use it as a render target.
2022-08-22 21:28:43 +02:00
DEBUG_LOG ( FRAMEBUF , " Switching render target to FBO for %08x: %d x %d x %d " , vfb - > fb_address , vfb - > width , vfb - > height , vfb - > fb_format ) ;
2022-08-17 19:55:19 +02:00
vfb - > usageFlags | = FB_USAGE_RENDER_COLOR ;
2014-09-09 22:56:54 -07:00
vfb - > last_frame_render = gpuStats . numFlips ;
frameLastFramebufUsed_ = gpuStats . numFlips ;
vfb - > dirtyAfterDisplay = true ;
2015-08-05 12:13:14 +02:00
if ( ( skipDrawReason & SKIPDRAW_SKIPFRAME ) = = 0 )
2014-09-09 22:56:54 -07:00
vfb - > reallyDirtyAfterDisplay = true ;
VirtualFramebuffer * prev = currentRenderVfb_ ;
currentRenderVfb_ = vfb ;
2015-08-05 11:51:24 +02:00
NotifyRenderFramebufferSwitched ( prev , vfb , params . isClearingDepth ) ;
2022-08-18 10:51:50 +02:00
CopyToColorFromOverlappingFramebuffers ( vfb ) ;
2022-08-01 23:55:58 +02:00
gstate_c . usingDepth = false ; // reset depth buffer tracking
2014-09-09 22:56:54 -07:00
} else {
2022-08-01 23:55:58 +02:00
// Something changed, but we still got the same framebuffer we were already rendering to.
// Might not be a lot to do here, we check in NotifyRenderFramebufferUpdated
2014-09-09 22:56:54 -07:00
vfb - > last_frame_render = gpuStats . numFlips ;
frameLastFramebufUsed_ = gpuStats . numFlips ;
vfb - > dirtyAfterDisplay = true ;
2015-08-05 12:13:14 +02:00
if ( ( skipDrawReason & SKIPDRAW_SKIPFRAME ) = = 0 )
2014-09-09 22:56:54 -07:00
vfb - > reallyDirtyAfterDisplay = true ;
2022-08-25 18:58:35 +02:00
NotifyRenderFramebufferUpdated ( vfb ) ;
2014-09-09 22:56:54 -07:00
}
2022-08-16 22:39:09 +02:00
vfb - > colorBindSeq = GetBindSeqCount ( ) ;
2014-09-09 22:56:54 -07:00
gstate_c . curRTWidth = vfb - > width ;
gstate_c . curRTHeight = vfb - > height ;
gstate_c . curRTRenderWidth = vfb - > renderWidth ;
gstate_c . curRTRenderHeight = vfb - > renderHeight ;
2015-08-05 02:43:40 +02:00
return vfb ;
2014-09-09 22:56:54 -07:00
}
2014-09-13 14:44:18 -07:00
2022-08-01 23:55:58 +02:00
// Called on the first use of depth in a render pass.
2022-08-20 09:46:15 +02:00
void FramebufferManagerCommon : : SetDepthFrameBuffer ( bool isClearingDepth ) {
2022-08-01 23:55:58 +02:00
if ( ! currentRenderVfb_ ) {
return ;
}
2022-09-22 09:57:53 +02:00
// First time use of this framebuffer's depth buffer.
2022-09-23 00:04:14 -07:00
bool newlyUsingDepth = ( currentRenderVfb_ - > usageFlags & FB_USAGE_RENDER_DEPTH ) = = 0 ;
2022-09-22 09:57:53 +02:00
currentRenderVfb_ - > usageFlags | = FB_USAGE_RENDER_DEPTH ;
2022-10-04 20:56:41 +02:00
uint32_t boundDepthBuffer = gstate . getDepthBufRawAddress ( ) | 0x04000000 ;
2022-10-08 17:50:18 -07:00
uint32_t boundDepthStride = gstate . DepthBufStride ( ) ;
if ( currentRenderVfb_ - > z_address ! = boundDepthBuffer | | currentRenderVfb_ - > z_stride ! = boundDepthStride ) {
if ( currentRenderVfb_ - > fb_address = = boundDepthBuffer ) {
// Disallow setting depth buffer to the same address as the color buffer, usually means it's not used.
WARN_LOG_N_TIMES ( z_reassign , 5 , G3D , " Ignoring color matching depth buffer at %08x " , boundDepthBuffer ) ;
boundDepthBuffer = 0 ;
boundDepthStride = 0 ;
}
2022-09-28 14:09:40 +02:00
WARN_LOG_N_TIMES ( z_reassign , 5 , G3D , " Framebuffer at %08x/%d has switched associated depth buffer from %08x to %08x, updating. " ,
2022-09-28 13:41:41 +02:00
currentRenderVfb_ - > fb_address , currentRenderVfb_ - > fb_stride , currentRenderVfb_ - > z_address , boundDepthBuffer ) ;
2022-09-28 14:09:40 +02:00
2022-09-28 13:41:41 +02:00
// Technically, here we should copy away the depth buffer to another framebuffer that uses that z_address, or maybe
// even write it back to RAM. However, this is rare. Silent Hill is one example, see #16126.
currentRenderVfb_ - > z_address = boundDepthBuffer ;
2022-10-08 17:50:18 -07:00
// Update the stride in case it changed.
currentRenderVfb_ - > z_stride = boundDepthStride ;
2022-10-03 19:02:16 +02:00
if ( currentRenderVfb_ - > fbo ) {
char tag [ 128 ] ;
FormatFramebufferName ( currentRenderVfb_ , tag , sizeof ( tag ) ) ;
currentRenderVfb_ - > fbo - > UpdateTag ( tag ) ;
}
2022-09-28 13:41:41 +02:00
}
2022-08-20 09:46:15 +02:00
// If this first draw call is anything other than a clear, "resolve" the depth buffer,
// by copying from any overlapping buffers with fresher content.
2022-09-22 09:57:53 +02:00
if ( ! isClearingDepth & & useBufferedRendering_ ) {
2022-08-20 09:46:15 +02:00
CopyToDepthFromOverlappingFramebuffers ( currentRenderVfb_ ) ;
2022-08-01 23:55:58 +02:00
2022-09-22 22:11:16 +02:00
// Need to upload the first line of depth buffers, for Burnout Dominator lens flares. See issue #11100 and comments to #16081.
// Might make this more generic and upload the whole depth buffer if we find it's needed for something.
2022-10-01 19:06:02 +02:00
if ( newlyUsingDepth & & draw_ - > GetDeviceCaps ( ) . fragmentShaderDepthWriteSupported ) {
2022-09-12 15:34:32 +02:00
// Sanity check the depth buffer pointer.
2022-09-22 10:48:05 +02:00
if ( Memory : : IsValidRange ( currentRenderVfb_ - > z_address , currentRenderVfb_ - > width * 2 ) ) {
const u16 * src = ( const u16 * ) Memory : : GetPointerUnchecked ( currentRenderVfb_ - > z_address ) ;
DrawPixels ( currentRenderVfb_ , 0 , 0 , ( const u8 * ) src , GE_FORMAT_DEPTH16 , currentRenderVfb_ - > z_stride , currentRenderVfb_ - > width , currentRenderVfb_ - > height , RASTER_DEPTH , " Depth Upload " ) ;
2022-09-12 15:34:32 +02:00
}
}
}
2022-09-15 09:15:02 +02:00
2022-08-01 23:55:58 +02:00
currentRenderVfb_ - > depthBindSeq = GetBindSeqCount ( ) ;
}
2022-08-18 10:51:50 +02:00
struct CopySource {
VirtualFramebuffer * vfb ;
RasterChannel channel ;
int xOffset ;
int yOffset ;
2022-08-17 12:11:00 +02:00
2022-08-18 10:51:50 +02:00
int seq ( ) const {
return channel = = RASTER_DEPTH ? vfb - > depthBindSeq : vfb - > colorBindSeq ;
}
2022-08-17 12:11:00 +02:00
2022-08-18 10:51:50 +02:00
bool operator < ( const CopySource & other ) const {
return seq ( ) < other . seq ( ) ;
}
} ;
2022-08-17 12:11:00 +02:00
2022-08-18 10:51:50 +02:00
// Not sure if it's more profitable to always do these copies with raster (which may screw up early-Z due to explicit depth buffer write)
// or to use image copies when possible (which may make it easier for the driver to preserve early-Z, but on the other hand, will cost additional memory
// bandwidth on tilers due to the load operation, which we might otherwise be able to skip).
void FramebufferManagerCommon : : CopyToDepthFromOverlappingFramebuffers ( VirtualFramebuffer * dest ) {
2022-08-17 12:11:00 +02:00
std : : vector < CopySource > sources ;
2022-08-17 19:55:19 +02:00
for ( auto src : vfbs_ ) {
2022-08-17 12:11:00 +02:00
if ( src = = dest )
continue ;
2022-08-22 21:28:43 +02:00
if ( src - > fb_address = = dest - > z_address & & src - > fb_stride = = dest - > z_stride & & src - > fb_format = = GE_FORMAT_565 ) {
2022-08-17 19:55:19 +02:00
if ( src - > colorBindSeq > dest - > depthBindSeq ) {
2022-08-17 14:28:34 +02:00
// Source has newer data than the current buffer, use it.
2022-08-18 10:51:50 +02:00
sources . push_back ( CopySource { src , RASTER_COLOR , 0 , 0 } ) ;
2022-08-17 12:11:00 +02:00
}
} else if ( src - > z_address = = dest - > z_address & & src - > z_stride = = dest - > z_stride & & src - > depthBindSeq > dest - > depthBindSeq ) {
2022-08-18 10:51:50 +02:00
sources . push_back ( CopySource { src , RASTER_DEPTH , 0 , 0 } ) ;
2022-08-17 12:11:00 +02:00
} else {
// TODO: Do more detailed overlap checks here.
}
}
2022-08-17 14:28:34 +02:00
std : : sort ( sources . begin ( ) , sources . end ( ) ) ;
// TODO: A full copy will overwrite anything else. So we can eliminate
2022-08-17 12:11:00 +02:00
// anything that comes before such a copy.
2022-08-17 14:28:34 +02:00
// For now, let's just do the last thing, if there are multiple.
// for (auto &source : sources) {
2022-08-19 00:34:02 +02:00
if ( ! sources . empty ( ) ) {
2022-12-01 19:15:38 +01:00
draw_ - > Invalidate ( InvalidationFlags : : CACHED_RENDER_STATE ) ;
2022-08-20 16:32:04 +02:00
2022-08-17 14:28:34 +02:00
auto & source = sources . back ( ) ;
2022-08-17 12:11:00 +02:00
if ( source . channel = = RASTER_DEPTH ) {
// Good old depth->depth copy.
BlitFramebufferDepth ( source . vfb , dest ) ;
gpuStats . numDepthCopies + + ;
dest - > last_frame_depth_updated = gpuStats . numFlips ;
2022-08-20 16:58:59 +02:00
} else if ( source . channel = = RASTER_COLOR & & draw_ - > GetDeviceCaps ( ) . fragmentShaderDepthWriteSupported ) {
2022-08-17 14:28:34 +02:00
VirtualFramebuffer * src = source . vfb ;
2022-08-22 23:30:28 +02:00
if ( src - > fb_format ! = GE_FORMAT_565 ) {
WARN_LOG_ONCE ( not565 , G3D , " fb_format of buffer at %08x not 565 as expected " , src - > fb_address ) ;
2022-08-20 17:23:51 +02:00
}
2022-08-19 00:34:02 +02:00
// Really hate to do this, but tracking the depth swizzle state across multiple
// copies is not easy.
Draw2DShader shader = DRAW2D_565_TO_DEPTH ;
if ( PSP_CoreParameter ( ) . compat . flags ( ) . DeswizzleDepth ) {
shader = DRAW2D_565_TO_DEPTH_DESWIZZLE ;
}
2022-08-18 10:51:50 +02:00
gpuStats . numReinterpretCopies + + ;
2022-12-12 23:23:14 -08:00
src - > usageFlags | = FB_USAGE_COLOR_MIXED_DEPTH ;
dest - > usageFlags | = FB_USAGE_COLOR_MIXED_DEPTH ;
2022-08-18 10:51:50 +02:00
2022-08-18 15:46:20 +02:00
// Copying color to depth.
BlitUsingRaster (
src - > fbo , 0.0f , 0.0f , src - > renderWidth , src - > renderHeight ,
dest - > fbo , 0.0f , 0.0f , src - > renderWidth , src - > renderHeight ,
2022-09-04 23:28:55 +02:00
false , dest - > renderScaleFactor , Get2DPipeline ( shader ) , " 565_to_depth " ) ;
2022-08-17 12:11:00 +02:00
}
}
2022-09-22 09:12:20 +02:00
gstate_c . Dirty ( DIRTY_ALL_RENDER_STATE ) ;
2022-08-17 12:11:00 +02:00
}
2022-08-22 23:30:28 +02:00
// Can't easily dynamically create these strings, we just pass along the pointer.
2022-08-27 23:37:02 +02:00
static const char * reinterpretStrings [ 4 ] [ 4 ] = {
2022-08-22 23:30:28 +02:00
{
" self_reinterpret_565 " ,
" reinterpret_565_to_5551 " ,
" reinterpret_565_to_4444 " ,
2022-08-27 23:37:02 +02:00
" reinterpret_565_to_8888 " ,
2022-08-22 23:30:28 +02:00
} ,
{
" reinterpret_5551_to_565 " ,
" self_reinterpret_5551 " ,
" reinterpret_5551_to_4444 " ,
2022-08-27 23:37:02 +02:00
" reinterpret_5551_to_8888 " ,
2022-08-22 23:30:28 +02:00
} ,
{
" reinterpret_4444_to_565 " ,
" reinterpret_4444_to_5551 " ,
" self_reinterpret_4444 " ,
2022-08-27 23:37:02 +02:00
" reinterpret_4444_to_8888 " ,
} ,
{
" reinterpret_8888_to_565 " ,
" reinterpret_8888_to_5551 " ,
" reinterpret_8888_to_4444 " ,
" self_reinterpret_8888 " ,
2022-08-22 23:30:28 +02:00
} ,
} ;
2022-08-18 10:51:50 +02:00
// Call this after the target has been bound for rendering. For color, raster is probably always going to win over blits/copies.
void FramebufferManagerCommon : : CopyToColorFromOverlappingFramebuffers ( VirtualFramebuffer * dst ) {
2022-09-13 00:29:46 +02:00
if ( ! useBufferedRendering_ ) {
return ;
}
2022-08-18 10:51:50 +02:00
std : : vector < CopySource > sources ;
for ( auto src : vfbs_ ) {
// Discard old and equal potential inputs.
2022-08-22 23:30:28 +02:00
if ( src = = dst | | src - > colorBindSeq < dst - > colorBindSeq ) {
2022-08-18 10:51:50 +02:00
continue ;
2022-08-22 23:30:28 +02:00
}
2022-08-18 10:51:50 +02:00
if ( src - > fb_address = = dst - > fb_address & & src - > fb_stride = = dst - > fb_stride ) {
2022-08-29 10:14:29 +02:00
// Another render target at the exact same location but gotta be a different format or a different stride, otherwise
// it would be the same, and should have been detected in DoSetRenderFrameBuffer.
if ( src - > fb_format ! = dst - > fb_format ) {
// This will result in reinterpret later, if both formats are 16-bit.
sources . push_back ( CopySource { src , RASTER_COLOR , 0 , 0 } ) ;
} else {
2022-08-30 20:10:44 +02:00
// This shouldn't happen anymore. I think when it happened last, we still had
// lax stride checking when video was incoming, and a resize happened causing a duplicate.
2022-08-29 10:14:29 +02:00
}
2022-08-22 21:28:43 +02:00
} else if ( src - > fb_stride = = dst - > fb_stride & & src - > fb_format = = dst - > fb_format ) {
u32 bytesPerPixel = BufferFormatBytesPerPixel ( src - > fb_format ) ;
2022-08-18 10:51:50 +02:00
u32 strideInBytes = src - > fb_stride * bytesPerPixel ; // Same for both src and dest
u32 srcColorStart = src - > fb_address ;
u32 srcFirstLineEnd = src - > fb_address + strideInBytes ;
u32 srcColorEnd = strideInBytes * src - > height ;
u32 dstColorStart = dst - > fb_address ;
u32 dstFirstLineEnd = dst - > fb_address + strideInBytes ;
u32 dstColorEnd = strideInBytes * dst - > height ;
// Initially we'll only allow pure horizontal and vertical overlap,
// to reduce the risk for false positives. We can allow diagonal overlap too if needed
// in the future.
// Check for potential vertical overlap, like in Juiced 2.
int xOffset = 0 ;
int yOffset = 0 ;
// TODO: Get rid of the compatibility flag check.
if ( ( dstColorStart - srcColorStart ) % strideInBytes = = 0
& & PSP_CoreParameter ( ) . compat . flags ( ) . AllowLargeFBTextureOffsets ) {
// Buffers are aligned.
yOffset = ( ( int ) dstColorStart - ( int ) srcColorStart ) / strideInBytes ;
if ( yOffset < = - ( int ) src - > height ) {
// Not overlapping
continue ;
} else if ( yOffset > = dst - > height ) {
// Not overlapping
continue ;
}
} else {
// Buffers not stride-aligned - ignoring for now.
2022-08-22 23:30:28 +02:00
// This is where we'll add the horizontal offset for GoW.
2022-08-18 10:51:50 +02:00
continue ;
}
sources . push_back ( CopySource { src , RASTER_COLOR , xOffset , yOffset } ) ;
2022-08-25 23:14:01 +02:00
} else if ( src - > fb_address = = dst - > fb_address & & src - > FbStrideInBytes ( ) = = dst - > FbStrideInBytes ( ) ) {
if ( src - > fb_stride = = dst - > fb_stride * 2 ) {
// Reinterpret from 16-bit to 32-bit.
2022-08-26 11:34:50 +02:00
sources . push_back ( CopySource { src , RASTER_COLOR , 0 , 0 } ) ;
2022-08-28 07:31:50 +02:00
} else if ( src - > fb_stride * 2 = = dst - > fb_stride ) {
2022-08-25 23:14:01 +02:00
// Reinterpret from 32-bit to 16-bit.
2022-08-26 11:34:50 +02:00
sources . push_back ( CopySource { src , RASTER_COLOR , 0 , 0 } ) ;
2022-08-25 23:14:01 +02:00
} else {
2022-08-26 11:34:50 +02:00
// 16-to-16 reinterpret, should have been caught above already.
2022-08-28 09:13:43 +02:00
_assert_msg_ ( false , " Reinterpret: Shouldn't get here " ) ;
2022-08-25 23:14:01 +02:00
}
2022-08-18 10:51:50 +02:00
}
}
std : : sort ( sources . begin ( ) , sources . end ( ) ) ;
2022-12-01 19:15:38 +01:00
draw_ - > Invalidate ( InvalidationFlags : : CACHED_RENDER_STATE ) ;
2022-08-18 10:51:50 +02:00
2022-08-23 13:09:29 +02:00
bool tookActions = false ;
2022-08-27 19:24:25 +02:00
// TODO: Only do the latest one.
2022-08-18 10:51:50 +02:00
for ( const CopySource & source : sources ) {
VirtualFramebuffer * src = source . vfb ;
// Copy a rectangle from the original to the new buffer.
// Yes, we mean to look at src->width/height for the dest rectangle.
2022-08-27 23:37:02 +02:00
// TODO: Try to bound the blit using gstate_c.vertBounds like depal does.
2022-08-18 10:51:50 +02:00
int srcWidth = src - > width * src - > renderScaleFactor ;
int srcHeight = src - > height * src - > renderScaleFactor ;
int dstWidth = src - > width * dst - > renderScaleFactor ;
int dstHeight = src - > height * dst - > renderScaleFactor ;
int dstX1 = - source . xOffset * dst - > renderScaleFactor ;
int dstY1 = - source . yOffset * dst - > renderScaleFactor ;
int dstX2 = dstX1 + dstWidth ;
int dstY2 = dstY1 + dstHeight ;
2022-08-22 23:30:28 +02:00
if ( source . channel = = RASTER_COLOR ) {
2022-08-23 13:09:29 +02:00
Draw2DPipeline * pipeline = nullptr ;
const char * pass_name = " N/A " ;
2022-08-22 23:30:28 +02:00
if ( src - > fb_format = = dst - > fb_format ) {
gpuStats . numColorCopies + + ;
2022-08-23 13:09:29 +02:00
pipeline = Get2DPipeline ( DRAW2D_COPY_COLOR ) ;
pass_name = " copy_color " ;
2022-08-29 15:57:20 +02:00
} else {
2022-08-26 11:34:50 +02:00
if ( PSP_CoreParameter ( ) . compat . flags ( ) . BlueToAlpha ) {
WARN_LOG_ONCE ( bta , G3D , " WARNING: Reinterpret encountered with BlueToAlpha on " ) ;
}
2022-08-26 12:16:56 +02:00
// Reinterpret!
2022-08-26 23:26:44 +02:00
WARN_LOG_N_TIMES ( reint , 5 , G3D , " Reinterpret detected from %08x_%s to %08x_%s " ,
2022-08-26 12:16:56 +02:00
src - > fb_address , GeBufferFormatToString ( src - > fb_format ) ,
dst - > fb_address , GeBufferFormatToString ( dst - > fb_format ) ) ;
2022-09-13 11:28:37 +02:00
float scaleFactorX = 1.0f ;
pipeline = GetReinterpretPipeline ( src - > fb_format , dst - > fb_format , & scaleFactorX ) ;
2022-09-14 08:58:06 +02:00
dstX1 * = scaleFactorX ;
dstX2 * = scaleFactorX ;
2022-09-13 11:28:37 +02:00
2022-08-26 12:16:56 +02:00
pass_name = reinterpretStrings [ ( int ) src - > fb_format ] [ ( int ) dst - > fb_format ] ;
2022-08-26 11:34:50 +02:00
gpuStats . numReinterpretCopies + + ;
2022-08-23 13:09:29 +02:00
}
2022-08-26 11:34:50 +02:00
2022-08-23 13:09:29 +02:00
if ( pipeline ) {
tookActions = true ;
2022-08-22 23:30:28 +02:00
// OK we have the pipeline, now just do the blit.
BlitUsingRaster ( src - > fbo , 0.0f , 0.0f , srcWidth , srcHeight ,
2022-09-04 23:28:55 +02:00
dst - > fbo , dstX1 , dstY1 , dstX2 , dstY2 , false , dst - > renderScaleFactor , pipeline , pass_name ) ;
2022-08-22 23:30:28 +02:00
}
}
2022-08-18 10:51:50 +02:00
}
2022-09-06 18:54:52 -07:00
if ( currentRenderVfb_ & & dst ! = currentRenderVfb_ & & tookActions ) {
2022-09-03 22:04:01 +02:00
// Will probably just change the name of the current renderpass, since one was started by the reinterpret itself.
2022-10-23 11:21:35 +02:00
draw_ - > BindFramebufferAsRenderTarget ( currentRenderVfb_ - > fbo , { Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP } , " After Reinterpret " ) ;
2022-08-23 13:09:29 +02:00
}
2022-08-22 23:30:28 +02:00
shaderManager_ - > DirtyLastShader ( ) ;
textureCache_ - > ForgetLastTexture ( ) ;
}
2022-08-18 10:51:50 +02:00
2022-09-13 11:28:37 +02:00
Draw2DPipeline * FramebufferManagerCommon : : GetReinterpretPipeline ( GEBufferFormat from , GEBufferFormat to , float * scaleFactorX ) {
2022-09-15 16:57:03 +02:00
if ( from = = to ) {
* scaleFactorX = 1.0f ;
return Get2DPipeline ( DRAW2D_COPY_COLOR ) ;
}
2022-09-13 11:28:37 +02:00
if ( IsBufferFormat16Bit ( from ) & & ! IsBufferFormat16Bit ( to ) ) {
// We halve the X coordinates in the destination framebuffer.
// The shader will collect two pixels worth of input data and merge into one.
* scaleFactorX = 0.5f ;
} else if ( ! IsBufferFormat16Bit ( from ) & & IsBufferFormat16Bit ( to ) ) {
// We double the X coordinates in the destination framebuffer.
// The shader will sample and depending on the X coordinate & 1, use the upper or lower bits.
* scaleFactorX = 2.0f ;
2022-09-15 16:57:03 +02:00
} else {
* scaleFactorX = 1.0f ;
2022-09-13 11:28:37 +02:00
}
Draw2DPipeline * pipeline = reinterpretFromTo_ [ ( int ) from ] [ ( int ) to ] ;
if ( ! pipeline ) {
pipeline = draw2D_ . Create2DPipeline ( [ = ] ( ShaderWriter & shaderWriter ) - > Draw2DPipelineInfo {
return GenerateReinterpretFragmentShader ( shaderWriter , from , to ) ;
} ) ;
reinterpretFromTo_ [ ( int ) from ] [ ( int ) to ] = pipeline ;
}
return pipeline ;
}
2017-02-06 12:02:30 +01:00
void FramebufferManagerCommon : : DestroyFramebuf ( VirtualFramebuffer * v ) {
2017-11-22 12:24:05 +01:00
// Notify the texture cache of both the color and depth buffers.
2020-09-20 21:46:40 +02:00
textureCache_ - > NotifyFramebuffer ( v , NOTIFY_FB_DESTROYED ) ;
2017-02-06 12:02:30 +01:00
if ( v - > fbo ) {
2017-02-17 19:22:41 +01:00
v - > fbo - > Release ( ) ;
2017-02-06 12:02:30 +01:00
v - > fbo = nullptr ;
}
// Wipe some pointers
if ( currentRenderVfb_ = = v )
2020-05-24 20:27:58 +02:00
currentRenderVfb_ = nullptr ;
2017-02-06 12:02:30 +01:00
if ( displayFramebuf_ = = v )
2020-05-24 20:27:58 +02:00
displayFramebuf_ = nullptr ;
2017-02-06 12:02:30 +01:00
if ( prevDisplayFramebuf_ = = v )
2020-05-24 20:27:58 +02:00
prevDisplayFramebuf_ = nullptr ;
2017-02-06 12:02:30 +01:00
if ( prevPrevDisplayFramebuf_ = = v )
2020-05-24 20:27:58 +02:00
prevPrevDisplayFramebuf_ = nullptr ;
2017-02-06 12:02:30 +01:00
delete v ;
}
2020-09-17 20:31:40 +02:00
void FramebufferManagerCommon : : BlitFramebufferDepth ( VirtualFramebuffer * src , VirtualFramebuffer * dst ) {
2021-10-17 21:51:45 -07:00
_dbg_assert_ ( src & & dst ) ;
2022-08-07 13:19:27 +02:00
_dbg_assert_ ( src ! = dst ) ;
2021-10-17 21:51:45 -07:00
// Check that the depth address is even the same before actually blitting.
bool matchingDepthBuffer = src - > z_address = = dst - > z_address & & src - > z_stride ! = 0 & & dst - > z_stride ! = 0 ;
2022-08-06 18:47:12 +02:00
bool matchingSize = ( src - > width = = dst - > width | | ( src - > width = = 512 & & dst - > width = = 480 ) | | ( src - > width = = 480 & & dst - > width = = 512 ) ) & & src - > height = = dst - > height ;
2022-07-17 18:14:55 +02:00
if ( ! matchingDepthBuffer | | ! matchingSize ) {
2021-10-17 21:51:45 -07:00
return ;
2022-07-17 18:14:55 +02:00
}
2021-10-17 21:51:45 -07:00
// Copy depth value from the previously bound framebuffer to the current one.
bool hasNewerDepth = src - > last_frame_depth_render ! = 0 & & src - > last_frame_depth_render > = dst - > last_frame_depth_updated ;
if ( ! src - > fbo | | ! dst - > fbo | | ! useBufferedRendering_ | | ! hasNewerDepth ) {
// 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.
return ;
}
2022-08-07 13:19:27 +02:00
bool useCopy = draw_ - > GetDeviceCaps ( ) . framebufferSeparateDepthCopySupported | | ( ! draw_ - > GetDeviceCaps ( ) . framebufferDepthBlitSupported & & draw_ - > GetDeviceCaps ( ) . framebufferCopySupported ) ;
bool useBlit = draw_ - > GetDeviceCaps ( ) . framebufferDepthBlitSupported ;
2022-11-17 18:32:38 -08:00
bool useRaster = draw_ - > GetDeviceCaps ( ) . fragmentShaderDepthWriteSupported & & draw_ - > GetDeviceCaps ( ) . textureDepthSupported ;
2022-07-24 17:12:43 +02:00
2022-12-02 15:26:29 +01:00
if ( src - > fbo - > MultiSampleLevel ( ) > 0 & & dst - > fbo - > MultiSampleLevel ( ) > 0 ) {
// If multisampling, we want to copy depth properly so we get all the samples, to avoid aliased edges.
// Can be seen in the fire in Jeanne D'arc, for example.
if ( useRaster & & useCopy ) {
useRaster = false ;
}
}
2020-09-20 23:37:44 +02:00
int w = std : : min ( src - > renderWidth , dst - > renderWidth ) ;
int h = std : : min ( src - > renderHeight , dst - > renderHeight ) ;
2022-08-01 23:21:14 +02:00
// Some GPUs can copy depth but only if stencil gets to come along for the ride. We only want to use this if there is no blit functionality.
2022-11-17 18:32:38 -08:00
if ( useRaster ) {
BlitUsingRaster ( src - > fbo , 0 , 0 , w , h , dst - > fbo , 0 , 0 , w , h , false , dst - > renderScaleFactor , Get2DPipeline ( Draw2DShader : : DRAW2D_COPY_DEPTH ) , " BlitDepthRaster " ) ;
} else if ( useCopy ) {
2022-09-22 09:12:20 +02:00
draw_ - > CopyFramebufferImage ( src - > fbo , 0 , 0 , 0 , 0 , dst - > fbo , 0 , 0 , 0 , 0 , w , h , 1 , Draw : : FB_DEPTH_BIT , " CopyFramebufferDepth " ) ;
2020-12-13 00:20:47 +01:00
RebindFramebuffer ( " After BlitFramebufferDepth " ) ;
2022-08-07 13:19:27 +02:00
} else if ( useBlit ) {
2022-08-01 23:21:14 +02:00
// We'll accept whether we get a separate depth blit or not...
draw_ - > BlitFramebuffer ( src - > fbo , 0 , 0 , w , h , dst - > fbo , 0 , 0 , w , h , Draw : : FB_DEPTH_BIT , Draw : : FB_BLIT_NEAREST , " BlitFramebufferDepth " ) ;
RebindFramebuffer ( " After BlitFramebufferDepth " ) ;
2020-09-17 20:31:40 +02:00
}
2022-08-07 13:19:27 +02:00
2022-12-01 19:15:38 +01:00
draw_ - > Invalidate ( InvalidationFlags : : CACHED_RENDER_STATE ) ;
2020-09-17 20:31:40 +02:00
}
2017-02-06 12:05:14 +01: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 10:01:43 -07:00
} else if ( currentRenderVfb_ ) {
DownloadFramebufferOnSwitch ( currentRenderVfb_ ) ;
2017-02-06 12:05:14 +01:00
}
2020-09-20 21:46:40 +02:00
textureCache_ - > NotifyFramebuffer ( vfb , NOTIFY_FB_CREATED ) ;
2017-02-06 12:05:14 +01:00
2022-08-25 18:58:35 +02:00
NotifyRenderFramebufferUpdated ( vfb ) ;
2017-02-06 12:05:14 +01:00
}
2022-08-25 18:58:35 +02:00
void FramebufferManagerCommon : : NotifyRenderFramebufferUpdated ( VirtualFramebuffer * vfb ) {
2017-02-06 12:10:08 +01:00
if ( gstate_c . curRTWidth ! = vfb - > width | | gstate_c . curRTHeight ! = vfb - > height ) {
2018-09-21 21:55:11 -07:00
gstate_c . Dirty ( DIRTY_PROJTHROUGHMATRIX | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE ) ;
2017-02-06 12:10:08 +01:00
}
if ( gstate_c . curRTRenderWidth ! = vfb - > renderWidth | | gstate_c . curRTRenderHeight ! = vfb - > renderHeight ) {
gstate_c . Dirty ( DIRTY_PROJMATRIX ) ;
gstate_c . Dirty ( DIRTY_PROJTHROUGHMATRIX ) ;
}
}
2023-02-04 13:28:59 +01:00
void FramebufferManagerCommon : : DownloadFramebufferOnSwitch ( VirtualFramebuffer * vfb ) {
if ( vfb & & vfb - > safeWidth > 0 & & vfb - > safeHeight > 0 & & ! ( vfb - > usageFlags & FB_USAGE_FIRST_FRAME_SAVED ) & & ! vfb - > memoryUpdated ) {
// Some games will draw to some memory once, and use it as a render-to-texture later.
// To support this, we save the first frame to memory when we have a safe w/h.
// Saving each frame would be slow.
// TODO: This type of download could be made async, for less stutter on framebuffer creation.
if ( ! g_Config . bSkipGPUReadbacks & & ! PSP_CoreParameter ( ) . compat . flags ( ) . DisableFirstFrameReadback ) {
2023-02-05 10:52:52 +01:00
ReadFramebufferToMemory ( vfb , 0 , 0 , vfb - > safeWidth , vfb - > safeHeight , RASTER_COLOR , Draw : : ReadbackMode : : BLOCK ) ;
2023-02-04 13:28:59 +01:00
vfb - > usageFlags = ( vfb - > usageFlags | FB_USAGE_DOWNLOAD | FB_USAGE_FIRST_FRAME_SAVED ) & ~ FB_USAGE_DOWNLOAD_CLEAR ;
vfb - > safeWidth = 0 ;
vfb - > safeHeight = 0 ;
}
}
}
bool FramebufferManagerCommon : : ShouldDownloadFramebufferColor ( const VirtualFramebuffer * vfb ) const {
// Dangan Ronpa hack
return PSP_CoreParameter ( ) . compat . flags ( ) . Force04154000Download & & vfb - > fb_address = = 0x04154000 ;
}
bool FramebufferManagerCommon : : ShouldDownloadFramebufferDepth ( const VirtualFramebuffer * vfb ) const {
// Download depth buffer for Syphon Filter lens flares
if ( ! PSP_CoreParameter ( ) . compat . flags ( ) . ReadbackDepth | | g_Config . bSkipGPUReadbacks ) {
return false ;
}
return ( vfb - > usageFlags & FB_USAGE_RENDER_DEPTH ) ! = 0 & & vfb - > width > = 480 & & vfb - > height > = 272 ;
}
2017-02-07 00:29:02 +01:00
void FramebufferManagerCommon : : NotifyRenderFramebufferSwitched ( VirtualFramebuffer * prevVfb , VirtualFramebuffer * vfb , bool isClearingDepth ) {
2023-02-07 19:28:16 +01:00
if ( prevVfb ) {
if ( ShouldDownloadFramebufferColor ( prevVfb ) & & ! prevVfb - > memoryUpdated ) {
2023-02-07 19:32:44 +01:00
ReadFramebufferToMemory ( prevVfb , 0 , 0 , prevVfb - > width , prevVfb - > height , RASTER_COLOR , Draw : : ReadbackMode : : OLD_DATA_OK ) ;
2023-02-07 19:28:16 +01:00
prevVfb - > usageFlags = ( prevVfb - > usageFlags | FB_USAGE_DOWNLOAD | FB_USAGE_FIRST_FRAME_SAVED ) & ~ FB_USAGE_DOWNLOAD_CLEAR ;
} else {
DownloadFramebufferOnSwitch ( prevVfb ) ;
}
2022-09-01 10:24:52 +02:00
2023-02-07 19:28:16 +01:00
if ( ShouldDownloadFramebufferDepth ( prevVfb ) ) {
2023-02-07 19:32:44 +01:00
ReadFramebufferToMemory ( prevVfb , 0 , 0 , prevVfb - > width , prevVfb - > height , RasterChannel : : RASTER_DEPTH , Draw : : ReadbackMode : : BLOCK ) ;
2023-02-07 19:28:16 +01:00
}
2023-02-04 13:28:59 +01:00
}
2017-02-07 00:29:02 +01:00
textureCache_ - > ForgetLastTexture ( ) ;
2018-07-28 11:09:01 +02:00
shaderManager_ - > DirtyLastShader ( ) ;
2017-02-07 00:29:02 +01:00
if ( useBufferedRendering_ ) {
if ( vfb - > fbo ) {
2018-07-28 11:09:01 +02:00
shaderManager_ - > DirtyLastShader ( ) ;
2022-10-23 11:21:35 +02:00
draw_ - > BindFramebufferAsRenderTarget ( vfb - > fbo , { Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP } , " FBSwitch " ) ;
2017-02-07 00:29:02 +01:00
} else {
2017-04-24 09:30:04 -07:00
// This should only happen very briefly when toggling useBufferedRendering_.
ResizeFramebufFBO ( vfb , vfb - > width , vfb - > height , true ) ;
2017-02-07 00:29:02 +01:00
}
} else {
if ( vfb - > fbo ) {
2017-04-24 09:30:04 -07:00
// This should only happen very briefly when toggling useBufferedRendering_.
2020-09-20 21:46:40 +02:00
textureCache_ - > NotifyFramebuffer ( vfb , NOTIFY_FB_DESTROYED ) ;
2017-11-05 12:45:02 -08:00
vfb - > fbo - > Release ( ) ;
2017-02-07 00:29:02 +01: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 21:46:40 +02:00
textureCache_ - > NotifyFramebuffer ( vfb , NOTIFY_FB_UPDATED ) ;
2017-02-07 00:29:02 +01:00
2022-08-25 18:58:35 +02:00
NotifyRenderFramebufferUpdated ( vfb ) ;
2017-02-07 00:29:02 +01:00
}
2022-10-09 13:49:41 -07:00
void FramebufferManagerCommon : : PerformWriteFormattedFromMemory ( u32 addr , int size , int stride , GEBufferFormat fmt ) {
2016-01-17 12:52:40 -08:00
// 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...
2022-08-29 10:14:29 +02:00
// Also, stride needs better handling.
VirtualFramebuffer * vfb = ResolveVFB ( addr , stride , fmt ) ;
2016-01-17 12:52:40 -08:00
if ( vfb ) {
2022-08-29 10:14:29 +02:00
// Let's count this as a "render". This will also force us to use the correct format.
vfb - > last_frame_render = gpuStats . numFlips ;
vfb - > colorBindSeq = GetBindSeqCount ( ) ;
2016-01-17 12:52:40 -08:00
2022-08-29 10:14:29 +02:00
if ( vfb - > fb_stride < stride ) {
DEBUG_LOG ( ME , " Changing stride for %08x from %d to %d " , addr , vfb - > fb_stride , stride ) ;
2022-08-18 10:51:50 +02:00
const int bpp = BufferFormatBytesPerPixel ( fmt ) ;
2022-08-29 10:14:29 +02:00
ResizeFramebufFBO ( vfb , stride , size / ( bpp * stride ) ) ;
2017-03-19 10:56:34 -07:00
// Resizing may change the viewport/etc.
2018-09-21 21:55:11 -07:00
gstate_c . Dirty ( DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE ) ;
2022-08-29 10:14:29 +02:00
vfb - > fb_stride = stride ;
2016-01-17 22:33:05 -08:00
// This might be a bit wider than necessary, but we'll redetect on next render.
2022-08-29 10:14:29 +02:00
vfb - > width = stride ;
2016-01-17 21:58:49 -08:00
}
2016-01-17 12:52:40 -08:00
}
}
2022-08-30 06:41:37 +02:00
void FramebufferManagerCommon : : UpdateFromMemory ( u32 addr , int size ) {
2018-11-11 10:54:28 +01:00
// Take off the uncached flag from the address. Not to be confused with the start of VRAM.
addr & = 0x3FFFFFFF ;
2022-10-02 21:28:53 -07:00
if ( Memory : : IsVRAMAddress ( addr ) )
addr & = 0x041FFFFF ;
2014-09-13 14:44:18 -07:00
// TODO: Could go through all FBOs, but probably not important?
// TODO: Could also check for inner changes, but video is most important.
2022-08-29 10:14:29 +02:00
// TODO: This shouldn't care if it's a display framebuf or not, should work exactly the same.
2022-09-20 14:01:36 -07:00
bool isDisplayBuf = addr = = CurrentDisplayFramebufAddr ( ) | | addr = = PrevDisplayFramebufAddr ( ) ;
2022-08-30 06:41:37 +02:00
// TODO: Deleting the FBO is a heavy hammer solution, so let's only do it if it'd help.
if ( ! Memory : : IsValidAddress ( displayFramebufPtr_ ) )
return ;
2014-09-13 14:44:18 -07:00
2022-08-30 06:41:37 +02:00
for ( size_t i = 0 ; i < vfbs_ . size ( ) ; + + i ) {
VirtualFramebuffer * vfb = vfbs_ [ i ] ;
if ( vfb - > fb_address = = addr ) {
FlushBeforeCopy ( ) ;
2014-09-13 14:44:18 -07:00
2022-08-30 06:41:37 +02:00
if ( useBufferedRendering_ & & vfb - > fbo ) {
GEBufferFormat fmt = vfb - > fb_format ;
if ( vfb - > last_frame_render + 1 < gpuStats . numFlips & & isDisplayBuf ) {
// If we're not rendering to it, format may be wrong. Use displayFormat_ instead.
// TODO: This doesn't seem quite right anymore.
fmt = displayFormat_ ;
2014-09-13 14:44:18 -07:00
}
2023-01-01 21:12:04 +01:00
DrawPixels ( vfb , 0 , 0 , Memory : : GetPointerUnchecked ( addr ) , fmt , vfb - > fb_stride , vfb - > width , vfb - > height , RASTER_COLOR , " UpdateFromMemory_DrawPixels " ) ;
2022-08-30 06:41:37 +02:00
SetColorUpdated ( vfb , gstate_c . skipDrawReason ) ;
} else {
INFO_LOG ( FRAMEBUF , " Invalidating FBO for %08x (%dx%d %s) " , vfb - > fb_address , vfb - > width , vfb - > height , GeBufferFormatToString ( vfb - > fb_format ) ) ;
DestroyFramebuf ( vfb ) ;
vfbs_ . erase ( vfbs_ . begin ( ) + i - - ) ;
2014-09-13 14:44:18 -07:00
}
}
}
2022-08-30 06:41:37 +02:00
RebindFramebuffer ( " RebindFramebuffer - UpdateFromMemory " ) ;
2017-03-19 10:25:30 -07:00
// TODO: Necessary?
2017-03-19 11:32:29 +01:00
gstate_c . Dirty ( DIRTY_FRAGMENTSHADER_STATE ) ;
2014-09-13 14:44:18 -07:00
}
2014-09-13 15:40:55 -07:00
2022-09-12 11:16:30 +02:00
void FramebufferManagerCommon : : DrawPixels ( VirtualFramebuffer * vfb , int dstX , int dstY , const u8 * srcPixels , GEBufferFormat srcPixelFormat , int srcStride , int width , int height , RasterChannel channel , const char * tag ) {
2017-02-15 18:32:44 +01:00
textureCache_ - > ForgetLastTexture ( ) ;
2022-09-12 15:34:32 +02:00
shaderManager_ - > DirtyLastShader ( ) ;
2017-03-22 20:56:26 -07:00
float u0 = 0.0f , u1 = 1.0f ;
2017-02-15 18:32:44 +01:00
float v0 = 0.0f , v1 = 1.0f ;
2017-03-22 20:56:26 -07:00
2020-05-11 19:25:33 -07:00
DrawTextureFlags flags ;
2017-02-15 18:32:44 +01:00
if ( useBufferedRendering_ & & vfb & & vfb - > fbo ) {
2022-09-26 20:47:55 +02:00
if ( channel = = RASTER_DEPTH | | PSP_CoreParameter ( ) . compat . flags ( ) . NearestFilteringOnFramebufferCreate ) {
flags = DRAWTEX_NEAREST ;
} else {
flags = DRAWTEX_LINEAR ;
}
2022-10-23 11:21:35 +02:00
draw_ - > BindFramebufferAsRenderTarget ( vfb - > fbo , { Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP } , tag ) ;
2017-02-15 18:32:44 +01:00
SetViewport2D ( 0 , 0 , vfb - > renderWidth , vfb - > renderHeight ) ;
2017-05-21 23:13:53 +02:00
draw_ - > SetScissorRect ( 0 , 0 , vfb - > renderWidth , vfb - > renderHeight ) ;
2017-02-15 18:32:44 +01:00
} else {
2022-09-12 15:34:32 +02:00
_dbg_assert_ ( channel = = RASTER_COLOR ) ;
2020-05-31 20:13:35 +02: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 18:32:44 +01:00
if ( needBackBufferYSwap_ )
std : : swap ( v0 , v1 ) ;
2020-05-11 19:25:33 -07:00
flags = g_Config . iBufFilter = = SCALE_LINEAR ? DRAWTEX_LINEAR : DRAWTEX_NEAREST ;
2019-06-22 22:15:09 +02:00
flags = flags | DRAWTEX_TO_BACKBUFFER ;
2020-07-05 22:46:04 +02:00
FRect frame = GetScreenFrame ( pixelWidth_ , pixelHeight_ ) ;
2020-05-31 19:45:28 +02:00
FRect rc ;
2020-05-31 20:13:35 +02:00
CenterDisplayOutputRect ( & rc , 480.0f , 272.0f , frame , ROTATION_LOCKED_HORIZONTAL ) ;
2020-05-31 19:45:28 +02:00
SetViewport2D ( rc . x , rc . y , rc . w , rc . h ) ;
2017-05-21 23:13:53 +02:00
draw_ - > SetScissorRect ( 0 , 0 , pixelWidth_ , pixelHeight_ ) ;
2017-02-15 18:32:44 +01:00
}
2022-09-12 15:34:32 +02:00
if ( channel = = RASTER_DEPTH ) {
_dbg_assert_ ( srcPixelFormat = = GE_FORMAT_DEPTH16 ) ;
flags = flags | DRAWTEX_DEPTH ;
2022-12-12 23:23:14 -08:00
if ( vfb )
vfb - > usageFlags | = FB_USAGE_COLOR_MIXED_DEPTH ;
2022-09-12 15:34:32 +02:00
}
2022-07-20 10:27:08 +02:00
Draw : : Texture * pixelsTex = MakePixelTexture ( srcPixels , srcPixelFormat , srcStride , width , height ) ;
2020-05-11 09:47:26 -07:00
if ( pixelsTex ) {
2022-10-25 23:32:28 +02:00
draw_ - > BindTextures ( 0 , 1 , & pixelsTex , Draw : : TextureBindFlags : : VULKAN_BIND_ARRAY ) ;
2022-09-12 15:34:32 +02:00
// TODO: Replace with draw2D_.Blit() directly.
2022-12-10 11:28:19 +01:00
DrawActiveTexture ( dstX , dstY , width , height ,
vfb ? vfb - > bufferWidth : pixel_xres ,
vfb ? vfb - > bufferHeight : pixel_yres ,
u0 , v0 , u1 , v1 , ROTATION_LOCKED_HORIZONTAL , flags ) ;
2022-09-12 15:34:32 +02:00
2020-05-11 09:47:26 -07:00
gpuStats . numUploads + + ;
pixelsTex - > Release ( ) ;
2022-12-01 19:15:38 +01:00
draw_ - > Invalidate ( InvalidationFlags : : CACHED_RENDER_STATE ) ;
2020-05-11 19:25:33 -07:00
2022-09-22 09:12:20 +02:00
gstate_c . Dirty ( DIRTY_ALL_RENDER_STATE ) ;
2020-05-11 09:47:26 -07:00
}
2017-02-15 18:32:44 +01:00
}
2022-10-18 00:26:10 +02:00
bool FramebufferManagerCommon : : BindFramebufferAsColorTexture ( int stage , VirtualFramebuffer * framebuffer , int flags , int layer ) {
2020-11-07 11:10:54 +01:00
if ( ! framebuffer - > fbo | | ! useBufferedRendering_ ) {
2020-11-29 07:59:35 -08:00
draw_ - > BindTexture ( stage , nullptr ) ;
2020-11-07 11:10:54 +01:00
gstate_c . skipDrawReason | = SKIPDRAW_BAD_FB_TEXTURE ;
return false ;
}
// currentRenderVfb_ will always be set when this is called, except from the GE debugger.
// Let's just not bother with the copy in that case.
2022-09-13 19:07:54 -07:00
bool skipCopy = ! ( flags & BINDFBCOLOR_MAY_COPY ) ;
2020-12-13 00:20:47 +01:00
2020-11-07 11:10:54 +01:00
// Currently rendering to this framebuffer. Need to make a copy.
if ( ! skipCopy & & framebuffer = = currentRenderVfb_ ) {
2022-08-29 15:57:39 +02:00
// Self-texturing, need a copy currently (some backends can potentially support it though).
2023-01-02 22:23:50 +01:00
WARN_LOG_ONCE ( selfTextureCopy , G3D , " Attempting to texture from current render target (src=%08x / target=%08x / flags=%d), making a copy " , framebuffer - > fb_address , currentRenderVfb_ - > fb_address , flags ) ;
2020-11-07 11:10:54 +01:00
// TODO: Maybe merge with bvfbs_? Not sure if those could be packing, and they're created at a different size.
Draw : : Framebuffer * renderCopy = GetTempFBO ( TempFBO : : COPY , framebuffer - > renderWidth , framebuffer - > renderHeight ) ;
if ( renderCopy ) {
VirtualFramebuffer copyInfo = * framebuffer ;
copyInfo . fbo = renderCopy ;
2022-10-18 00:26:10 +02:00
CopyFramebufferForColorTexture ( & copyInfo , framebuffer , flags , layer ) ;
2020-11-07 11:10:54 +01:00
RebindFramebuffer ( " After BindFramebufferAsColorTexture " ) ;
2022-10-18 00:26:10 +02:00
draw_ - > BindFramebufferAsTexture ( renderCopy , stage , Draw : : FB_COLOR_BIT , layer ) ;
2022-08-29 15:57:39 +02:00
gpuStats . numCopiesForSelfTex + + ;
2020-11-07 11:10:54 +01:00
} else {
2022-08-29 15:57:39 +02:00
// Failed to get temp FBO? Weird.
2022-10-18 00:26:10 +02:00
draw_ - > BindFramebufferAsTexture ( framebuffer - > fbo , stage , Draw : : FB_COLOR_BIT , layer ) ;
2020-11-07 11:10:54 +01:00
}
return true ;
} else if ( framebuffer ! = currentRenderVfb_ | | ( flags & BINDFBCOLOR_FORCE_SELF ) ! = 0 ) {
2022-10-18 00:26:10 +02:00
draw_ - > BindFramebufferAsTexture ( framebuffer - > fbo , stage , Draw : : FB_COLOR_BIT , layer ) ;
2020-11-07 11:10:54 +01:00
return true ;
} else {
2023-01-02 22:23:50 +01:00
// Here it's an error because for some reason skipCopy is true. That shouldn't really happen.
2022-08-29 15:57:39 +02:00
ERROR_LOG_REPORT_ONCE ( selfTextureFail , G3D , " Attempting to texture from target (src=%08x / target=%08x / flags=%d) " , framebuffer - > fb_address , currentRenderVfb_ - > fb_address , flags ) ;
2020-11-07 11:10:54 +01:00
// To do this safely in Vulkan, we need to use input attachments.
// Actually if the texture region and render regions don't overlap, this is safe, but we need
// to transition to GENERAL image layout which will take some trickery.
// Badness on D3D11 to bind the currently rendered-to framebuffer as a texture.
2020-11-29 07:59:35 -08:00
draw_ - > BindTexture ( stage , nullptr ) ;
2020-11-07 11:10:54 +01:00
gstate_c . skipDrawReason | = SKIPDRAW_BAD_FB_TEXTURE ;
return false ;
}
}
2022-10-18 00:26:10 +02:00
void FramebufferManagerCommon : : CopyFramebufferForColorTexture ( VirtualFramebuffer * dst , VirtualFramebuffer * src , int flags , int layer ) {
2017-04-06 18:49:48 -07:00
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 ;
}
2021-02-28 08:19:06 -08:00
// We'll have to reapply these next time since we cropped to UV.
gstate_c . Dirty ( DIRTY_TEXTURE_PARAMS ) ;
2017-04-06 18:49:48 -07:00
}
if ( x < src - > drawnWidth & & y < src - > drawnHeight & & w > 0 & & h > 0 ) {
2022-10-04 17:56:30 +02:00
BlitFramebuffer ( dst , x , y , src , x , y , w , h , 0 , RASTER_COLOR , " CopyFBForColorTexture " ) ;
2017-04-06 18:49:48 -07:00
}
}
2022-07-20 10:27:08 +02:00
Draw : : Texture * FramebufferManagerCommon : : MakePixelTexture ( const u8 * srcPixels , GEBufferFormat srcPixelFormat , int srcStride , int width , int height ) {
2022-11-29 12:37:41 +01:00
Draw : : DataFormat depthFormat = Draw : : DataFormat : : UNDEFINED ;
if ( srcPixelFormat = = GE_FORMAT_DEPTH16 ) {
if ( ( draw_ - > GetDataFormatSupport ( Draw : : DataFormat : : R16_UNORM ) & Draw : : FMT_TEXTURE ) ! = 0 ) {
depthFormat = Draw : : DataFormat : : R16_UNORM ;
} else if ( ( draw_ - > GetDataFormatSupport ( Draw : : DataFormat : : R8_UNORM ) & Draw : : FMT_TEXTURE ) ! = 0 ) {
// This could be improved by using specific draw shaders to pack full precision in two channels.
// However, not really worth the trouble until we find a game that requires it.
depthFormat = Draw : : DataFormat : : R8_UNORM ;
} else {
// No usable single channel format. Can't be bothered.
return nullptr ;
}
}
2020-05-11 13:36:23 -07:00
// 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 ) ;
2022-09-12 15:34:32 +02:00
u16 * dst16 = ( u16 * ) ( data + byteStride * y ) ;
2022-11-29 12:37:41 +01:00
u8 * dst8 = ( u8 * ) ( data + byteStride * y ) ;
2020-05-11 13:36:23 -07:00
switch ( srcPixelFormat ) {
case GE_FORMAT_565 :
if ( preferredPixelsFormat_ = = Draw : : DataFormat : : B8G8R8A8_UNORM )
ConvertRGB565ToBGRA8888 ( dst , src16 , width ) ;
else
2020-05-13 18:17:58 -07:00
ConvertRGB565ToRGBA8888 ( dst , src16 , width ) ;
2020-05-11 13:36:23 -07: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-13 20:30:24 -07:00
// This means use original pointer as-is. May avoid or optimize a copy.
2020-05-14 18:45:06 -07:00
else if ( srcStride = = width )
2020-05-13 20:30:24 -07:00
return false ;
2020-05-14 18:45:06 -07:00
else
memcpy ( dst , src32 , width * 4 ) ;
2020-05-11 13:36:23 -07:00
break ;
2020-08-19 21:18:44 -07:00
case GE_FORMAT_DEPTH16 :
2022-09-12 15:34:32 +02:00
// TODO: Must take the depth range into account, unless it's already 0-1.
// TODO: Depending on the color buffer format used with this depth buffer, we need
// to do one of two different swizzle operations. However, for the only use of this so far,
// the Burnout lens flare trickery, swizzle doesn't matter since it's just a 0, 7fff, 0, 7fff pattern
// which comes out the same.
2022-11-29 12:37:41 +01:00
if ( depthFormat = = Draw : : DataFormat : : R16_UNORM ) {
// We just use this format straight.
memcpy ( dst16 , src16 , w * 2 ) ;
} else if ( depthFormat = = Draw : : DataFormat : : R8_UNORM ) {
// We fall back to R8_UNORM. Precision is enough for most cases of depth clearing and initialization we've seen,
// but hardly ideal.
for ( int i = 0 ; i < width ; i + + ) {
dst8 [ i ] = src16 [ i ] > > 8 ;
}
}
2022-09-12 15:34:32 +02:00
break ;
case GE_FORMAT_INVALID :
2022-10-11 09:55:53 +02:00
case GE_FORMAT_CLUT8 :
2022-09-12 15:34:32 +02:00
// Bad
2020-05-11 13:36:23 -07:00
break ;
}
}
2020-05-13 20:30:24 -07:00
return true ;
2020-05-11 13:36:23 -07:00
} ;
2022-09-12 15:34:32 +02:00
// Note: For depth, we create an R16_UNORM texture, that'll be just fine for uploading depth through a shader,
// and likely more efficient.
2020-05-11 13:36:23 -07:00
Draw : : TextureDesc desc {
Draw : : TextureType : : LINEAR2D ,
2022-11-29 12:37:41 +01:00
srcPixelFormat = = GE_FORMAT_DEPTH16 ? depthFormat : preferredPixelsFormat_ ,
2020-05-11 13:36:23 -07:00
width ,
height ,
1 ,
1 ,
false ,
" DrawPixels " ,
{ ( uint8_t * ) srcPixels } ,
generateTexture ,
} ;
2022-09-12 15:34:32 +02:00
2021-12-08 21:57:32 +01:00
// Hot Shots Golf (#12355) does tons of these in a frame in some situations! So creating textures
// better be fast.
2020-05-11 13:36:23 -07:00
Draw : : Texture * tex = draw_ - > CreateTexture ( desc ) ;
if ( ! tex )
2022-09-12 11:16:30 +02:00
ERROR_LOG ( G3D , " Failed to create DrawPixels texture " ) ;
2020-05-11 13:36:23 -07:00
return tex ;
}
2022-08-29 10:14:29 +02:00
void FramebufferManagerCommon : : DrawFramebufferToOutput ( const u8 * srcPixels , int srcStride , GEBufferFormat srcPixelFormat ) {
2017-02-15 23:11:46 +01:00
textureCache_ - > ForgetLastTexture ( ) ;
shaderManager_ - > DirtyLastShader ( ) ;
2017-03-22 20:56:26 -07:00
float u0 = 0.0f , u1 = 480.0f / 512.0f ;
float v0 = 0.0f , v1 = 1.0f ;
2022-07-20 10:27:08 +02:00
Draw : : Texture * pixelsTex = MakePixelTexture ( srcPixels , srcPixelFormat , srcStride , 512 , 272 ) ;
2020-05-11 09:47:26 -07:00
if ( ! pixelsTex )
return ;
2017-02-15 23:11:46 +01:00
2017-04-24 11:57:16 -07:00
int uvRotation = useBufferedRendering_ ? g_Config . iInternalScreenRotation : ROTATION_LOCKED_HORIZONTAL ;
2020-05-11 19:11:43 -07:00
OutputFlags flags = g_Config . iBufFilter = = SCALE_LINEAR ? OutputFlags : : LINEAR : OutputFlags : : NEAREST ;
if ( needBackBufferYSwap_ ) {
flags | = OutputFlags : : BACKBUFFER_FLIPPED ;
}
2022-09-12 15:34:32 +02:00
// CopyToOutput reverses these, probably to match "up".
2020-05-11 19:11:43 -07:00
if ( GetGPUBackend ( ) = = GPUBackend : : DIRECT3D9 | | GetGPUBackend ( ) = = GPUBackend : : DIRECT3D11 ) {
2020-05-11 23:28:50 -07:00
flags | = OutputFlags : : POSITION_FLIPPED ;
2020-05-11 13:36:23 -07:00
}
2020-05-10 16:53:15 -07:00
2020-05-15 23:41:13 -07:00
presentation_ - > UpdateUniforms ( textureCache_ - > VideoIsPlaying ( ) ) ;
presentation_ - > SourceTexture ( pixelsTex , 512 , 272 ) ;
presentation_ - > CopyToOutput ( flags , uvRotation , u0 , v0 , u1 , v1 ) ;
2020-05-11 09:47:26 -07:00
pixelsTex - > Release ( ) ;
2020-05-10 16:53:15 -07:00
// PresentationCommon sets all kinds of state, we can't rely on anything.
gstate_c . Dirty ( DIRTY_ALL ) ;
2020-12-13 00:20:47 +01:00
currentRenderVfb_ = nullptr ;
2017-02-15 23:11:46 +01:00
}
2017-03-05 13:59:16 +01: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 13:55:28 -08:00
void FramebufferManagerCommon : : CopyDisplayToOutput ( bool reallyDirty ) {
2017-11-05 08:13:43 -08:00
DownloadFramebufferOnSwitch ( currentRenderVfb_ ) ;
2018-07-28 11:09:01 +02:00
shaderManager_ - > DirtyLastShader ( ) ;
2017-02-15 23:24:25 +01:00
if ( displayFramebufPtr_ = = 0 ) {
2022-11-26 18:53:13 +01:00
if ( GetUIState ( ) ! = UISTATE_PAUSEMENU ) {
if ( Core_IsStepping ( ) )
VERBOSE_LOG ( FRAMEBUF , " Display disabled, displaying only black " ) ;
else
DEBUG_LOG ( FRAMEBUF , " Display disabled, displaying only black " ) ;
}
2017-02-15 23:24:25 +01:00
// No framebuffer to display! Clear to black.
2017-05-16 17:20:22 +02:00
if ( useBufferedRendering_ ) {
2022-10-23 11:21:35 +02:00
draw_ - > BindFramebufferAsRenderTarget ( nullptr , { Draw : : RPAction : : CLEAR , Draw : : RPAction : : CLEAR , Draw : : RPAction : : CLEAR } , " CopyDisplayToOutput " ) ;
2017-05-16 17:20:22 +02:00
}
2020-05-24 19:21:46 +02:00
gstate_c . Dirty ( DIRTY_VIEWPORTSCISSOR_STATE ) ;
2017-02-15 23:24:25 +01:00
return ;
}
u32 offsetX = 0 ;
u32 offsetY = 0 ;
2020-03-01 13:55:28 -08:00
// If it's not really dirty, we're probably frameskipping. Use the last working one.
u32 fbaddr = reallyDirty ? displayFramebufPtr_ : prevDisplayFramebufPtr_ ;
prevDisplayFramebufPtr_ = fbaddr ;
2022-08-29 10:14:29 +02:00
VirtualFramebuffer * vfb = ResolveVFB ( fbaddr , displayStride_ , displayFormat_ ) ;
2017-02-15 23:24:25 +01:00
if ( ! vfb ) {
2018-11-11 10:54:28 +01:00
// Let's search for a framebuf within this range. Note that we also look for
2019-09-17 14:45:40 +02: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.
2022-10-02 21:28:53 -07:00
const u32 addr = fbaddr ;
2022-08-25 00:52:45 +02:00
for ( auto v : vfbs_ ) {
2022-10-02 21:28:53 -07:00
const u32 v_addr = v - > fb_address ;
2019-09-17 14:45:40 +02:00
const u32 v_size = ColorBufferByteSize ( v ) ;
2022-10-01 23:53:13 +02:00
if ( v - > fb_format ! = displayFormat_ | | v - > fb_stride ! = displayStride_ ) {
// Displaying a buffer of the wrong format or stride is nonsense, ignore it.
continue ;
}
2017-02-15 23:24:25 +01:00
if ( addr > = v_addr & & addr < v_addr + v_size ) {
2022-08-22 21:28:43 +02:00
const u32 dstBpp = BufferFormatBytesPerPixel ( v - > fb_format ) ;
2017-02-15 23:24:25 +01:00
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 11:51:22 +02: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 23:24:25 +01:00
}
}
if ( ! vfb ) {
2020-03-01 13:55:28 -08:00
if ( Memory : : IsValidAddress ( fbaddr ) ) {
2017-02-15 23:24:25 +01:00
// The game is displaying something directly from RAM. In GTA, it's decoded video.
if ( ! vfb ) {
2023-01-01 21:12:04 +01:00
DrawFramebufferToOutput ( Memory : : GetPointerUnchecked ( fbaddr ) , displayStride_ , displayFormat_ ) ;
2017-02-15 23:24:25 +01:00
return ;
}
} else {
2020-03-01 13:55:28 -08:00
DEBUG_LOG ( FRAMEBUF , " Found no FBO to display! displayFBPtr = %08x " , fbaddr ) ;
2017-02-15 23:24:25 +01:00
// No framebuffer to display! Clear to black.
2017-05-16 16:00:34 +02:00
if ( useBufferedRendering_ ) {
// Bind and clear the backbuffer. This should be the first time during the frame that it's bound.
2022-10-23 11:21:35 +02:00
draw_ - > BindFramebufferAsRenderTarget ( nullptr , { Draw : : RPAction : : CLEAR , Draw : : RPAction : : CLEAR , Draw : : RPAction : : CLEAR } , " CopyDisplayToOutput_NoFBO " ) ;
2017-05-16 16:00:34 +02:00
}
2020-05-24 19:21:46 +02:00
gstate_c . Dirty ( DIRTY_VIEWPORTSCISSOR_STATE ) ;
2017-02-15 23:24:25 +01: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 ) {
2022-11-26 18:53:13 +01:00
if ( GetUIState ( ) ! = UISTATE_PAUSEMENU ) {
if ( Core_IsStepping ( ) )
VERBOSE_LOG ( FRAMEBUF , " Displaying FBO %08x " , vfb - > fb_address ) ;
else
DEBUG_LOG ( FRAMEBUF , " Displaying FBO %08x " , vfb - > fb_address ) ;
}
2017-02-15 23:24:25 +01:00
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 ;
2022-11-22 21:49:52 +01:00
//clip the VR framebuffer to keep the aspect ratio
if ( IsVREnabled ( ) & & ! IsFlatVRGame ( ) & & ! IsGameVRScene ( ) ) {
float aspect = 272.0f / 480.0f ;
float clipY = 272.0f * ( 1.0f - aspect ) / 2.0f ;
v0 = ( clipY + offsetY ) / ( float ) vfb - > bufferHeight ;
v1 = ( 272.0f - clipY + offsetY ) / ( float ) vfb - > bufferHeight ;
//zoom inside
float zoom = 0.1f ;
u0 + = zoom / aspect ;
u1 - = zoom / aspect ;
v0 + = zoom ;
v1 - = zoom ;
}
2020-05-10 16:53:15 -07:00
textureCache_ - > ForgetLastTexture ( ) ;
2020-05-11 19:25:33 -07:00
int uvRotation = useBufferedRendering_ ? g_Config . iInternalScreenRotation : ROTATION_LOCKED_HORIZONTAL ;
2020-05-10 07:29:28 -07:00
OutputFlags flags = g_Config . iBufFilter = = SCALE_LINEAR ? OutputFlags : : LINEAR : OutputFlags : : NEAREST ;
if ( needBackBufferYSwap_ ) {
flags | = OutputFlags : : BACKBUFFER_FLIPPED ;
}
2020-05-11 23:28:50 -07:00
// DrawActiveTexture reverses these, probably to match "up".
if ( GetGPUBackend ( ) = = GPUBackend : : DIRECT3D9 | | GetGPUBackend ( ) = = GPUBackend : : DIRECT3D11 ) {
flags | = OutputFlags : : POSITION_FLIPPED ;
}
2020-05-10 07:29:28 -07:00
2020-05-10 16:53:15 -07:00
int actualWidth = ( vfb - > bufferWidth * vfb - > renderWidth ) / vfb - > width ;
int actualHeight = ( vfb - > bufferHeight * vfb - > renderHeight ) / vfb - > height ;
2020-05-15 23:41:13 -07:00
presentation_ - > UpdateUniforms ( textureCache_ - > VideoIsPlaying ( ) ) ;
presentation_ - > SourceFramebuffer ( vfb - > fbo , actualWidth , actualHeight ) ;
presentation_ - > CopyToOutput ( flags , uvRotation , u0 , v0 , u1 , v1 ) ;
2020-05-10 00:38:19 -07:00
} else if ( useBufferedRendering_ ) {
2017-03-13 12:32:21 +01:00
WARN_LOG ( FRAMEBUF , " Current VFB lacks an FBO: %08x " , vfb - > fb_address ) ;
}
2018-01-20 07:57:38 -08:00
// This may get called mid-draw if the game uses an immediate flip.
2020-05-10 16:53:15 -07:00
// PresentationCommon sets all kinds of state, we can't rely on anything.
gstate_c . Dirty ( DIRTY_ALL ) ;
2021-12-08 21:57:32 +01:00
currentRenderVfb_ = nullptr ;
2017-02-15 23:24:25 +01:00
}
2017-02-07 00:42:39 +01:00
void FramebufferManagerCommon : : DecimateFBOs ( ) {
2020-11-27 23:34:02 +01:00
currentRenderVfb_ = nullptr ;
2017-02-07 00:42:39 +01:00
2017-10-25 20:28:12 +02:00
for ( auto iter : fbosToDelete_ ) {
2017-11-05 08:13:18 -08:00
iter - > Release ( ) ;
2017-10-25 20:28:12 +02:00
}
fbosToDelete_ . clear ( ) ;
2017-02-07 00:42:39 +01: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 ) ;
2023-02-04 13:28:59 +01:00
if ( ShouldDownloadFramebufferColor ( vfb ) & & age = = 0 & & ! vfb - > memoryUpdated ) {
2023-02-05 10:52:52 +01:00
ReadFramebufferToMemory ( vfb , 0 , 0 , vfb - > width , vfb - > height , RASTER_COLOR , Draw : : ReadbackMode : : BLOCK ) ;
2022-08-16 22:39:09 +02:00
vfb - > usageFlags = ( vfb - > usageFlags | FB_USAGE_DOWNLOAD | FB_USAGE_FIRST_FRAME_SAVED ) & ~ FB_USAGE_DOWNLOAD_CLEAR ;
2017-02-07 00:42:39 +01:00
}
// Let's also "decimate" the usageFlags.
UpdateFramebufUsage ( vfb ) ;
if ( vfb ! = displayFramebuf_ & & vfb ! = prevDisplayFramebuf_ & & vfb ! = prevPrevDisplayFramebuf_ ) {
if ( age > FBO_OLD_AGE ) {
2022-08-29 10:14:29 +02:00
INFO_LOG ( FRAMEBUF , " Decimating FBO for %08x (%ix%i %s), age %i " , vfb - > fb_address , vfb - > width , vfb - > height , GeBufferFormatToString ( vfb - > fb_format ) , age ) ;
2017-02-07 00:42:39 +01: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 19:22:41 +01:00
it - > second . fbo - > Release ( ) ;
2018-06-03 08:08:45 -07:00
it = tempFBOs_ . erase ( it ) ;
2017-02-07 00:42:39 +01: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 ) {
2022-08-29 10:14:29 +02:00
INFO_LOG ( FRAMEBUF , " Decimating FBO for %08x (%dx%d %s), age %i " , vfb - > fb_address , vfb - > width , vfb - > height , GeBufferFormatToString ( vfb - > fb_format ) , age ) ;
2017-02-07 00:42:39 +01:00
DestroyFramebuf ( vfb ) ;
bvfbs_ . erase ( bvfbs_ . begin ( ) + i - - ) ;
}
}
}
2017-02-07 00:38:12 +01:00
2020-11-05 11:21:00 +01:00
// Requires width/height to be set already.
2017-11-04 11:41:44 +01:00
void FramebufferManagerCommon : : ResizeFramebufFBO ( VirtualFramebuffer * vfb , int w , int h , bool force , bool skipCopy ) {
2020-08-16 00:38:55 +02:00
_dbg_assert_ ( w > 0 ) ;
_dbg_assert_ ( h > 0 ) ;
2017-02-07 00:38:12 +01:00
VirtualFramebuffer old = * vfb ;
2017-10-25 21:19:42 +02:00
int oldWidth = vfb - > bufferWidth ;
int oldHeight = vfb - > bufferHeight ;
2017-02-07 00:38:12 +01: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 11:41:44 +01:00
vfb - > bufferWidth = std : : max ( ( int ) vfb - > bufferWidth , w ) ;
vfb - > bufferHeight = std : : max ( ( int ) vfb - > bufferHeight , h ) ;
2017-02-07 00:38:12 +01:00
}
2020-11-05 11:21:00 +01:00
bool force1x = false ;
switch ( bloomHack_ ) {
case 1 :
force1x = vfb - > bufferWidth < = 128 | | vfb - > bufferHeight < = 64 ;
break ;
case 2 :
force1x = vfb - > bufferWidth < = 256 | | vfb - > bufferHeight < = 128 ;
break ;
case 3 :
force1x = vfb - > bufferWidth < 480 | | vfb - > bufferWidth > 800 | | vfb - > bufferHeight < 272 ; // GOW uses 864x272
break ;
}
2023-02-04 18:01:53 +01:00
if ( ( vfb - > usageFlags & FB_USAGE_COLOR_MIXED_DEPTH ) & & ! PSP_CoreParameter ( ) . compat . flags ( ) . ForceLowerResolutionForEffectsOn ) {
2022-12-12 23:23:14 -08:00
force1x = false ;
}
2020-11-05 11:21:00 +01:00
if ( PSP_CoreParameter ( ) . compat . flags ( ) . Force04154000Download & & vfb - > fb_address = = 0x04154000 ) {
force1x = true ;
}
if ( force1x & & g_Config . iInternalResolution ! = 1 ) {
2022-08-16 22:39:09 +02:00
vfb - > renderScaleFactor = 1 ;
2020-11-05 11:21:00 +01:00
vfb - > renderWidth = vfb - > bufferWidth ;
vfb - > renderHeight = vfb - > bufferHeight ;
} else {
vfb - > renderScaleFactor = renderScaleFactor_ ;
vfb - > renderWidth = ( u16 ) ( vfb - > bufferWidth * renderScaleFactor_ ) ;
vfb - > renderHeight = ( u16 ) ( vfb - > bufferHeight * renderScaleFactor_ ) ;
}
2017-02-07 00:38:12 +01:00
2022-08-27 13:57:36 +02:00
bool creating = old . bufferWidth = = 0 ;
2022-08-29 10:14:29 +02:00
if ( creating ) {
2022-09-28 13:41:41 +02:00
WARN_LOG ( FRAMEBUF , " Creating %s FBO at %08x/%08x stride=%d %dx%d (force=%d) " , GeBufferFormatToString ( vfb - > fb_format ) , vfb - > fb_address , vfb - > z_address , vfb - > fb_stride , vfb - > bufferWidth , vfb - > bufferHeight , ( int ) force ) ;
2022-08-29 10:14:29 +02:00
} else {
2022-09-28 13:41:41 +02:00
WARN_LOG ( FRAMEBUF , " Resizing %s FBO at %08x/%08x stride=%d from %dx%d to %dx%d (force=%d, skipCopy=%d) " , GeBufferFormatToString ( vfb - > fb_format ) , vfb - > fb_address , vfb - > z_address , vfb - > fb_stride , old . bufferWidth , old . bufferHeight , vfb - > bufferWidth , vfb - > bufferHeight , ( int ) force , ( int ) skipCopy ) ;
2022-08-29 10:14:29 +02:00
}
2022-08-27 13:57:36 +02:00
2018-12-14 14:01:08 +01: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.
2017-02-07 00:38:12 +01:00
textureCache_ - > ForgetLastTexture ( ) ;
if ( ! useBufferedRendering_ ) {
if ( vfb - > fbo ) {
2017-11-05 12:45:02 -08:00
vfb - > fbo - > Release ( ) ;
2017-02-07 00:38:12 +01:00
vfb - > fbo = nullptr ;
}
return ;
}
2017-04-24 09:30:04 -07: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-07 00:38:12 +01:00
2018-07-28 11:09:01 +02:00
shaderManager_ - > DirtyLastShader ( ) ;
2021-03-13 17:51:40 +01:00
char tag [ 128 ] ;
2022-08-23 13:09:29 +02:00
size_t len = FormatFramebufferName ( vfb , tag , sizeof ( tag ) ) ;
2022-11-28 11:50:28 +01:00
2022-12-14 15:15:31 +01:00
vfb - > fbo = draw_ - > CreateFramebuffer ( { vfb - > renderWidth , vfb - > renderHeight , 1 , GetFramebufferLayers ( ) , msaaLevel_ , true , tag } ) ;
2021-02-03 20:10:14 -08:00
if ( Memory : : IsVRAMAddress ( vfb - > fb_address ) & & vfb - > fb_stride ! = 0 ) {
2021-03-13 17:51:40 +01:00
NotifyMemInfo ( MemBlockFlags : : ALLOC , vfb - > fb_address , ColorBufferByteSize ( vfb ) , tag , len ) ;
2021-02-03 20:10:14 -08:00
}
if ( Memory : : IsVRAMAddress ( vfb - > z_address ) & & vfb - > z_stride ! = 0 ) {
2021-03-13 17:51:40 +01:00
char buf [ 128 ] ;
size_t len = snprintf ( buf , sizeof ( buf ) , " Z_%s " , tag ) ;
NotifyMemInfo ( MemBlockFlags : : ALLOC , vfb - > z_address , vfb - > fb_stride * vfb - > height * sizeof ( uint16_t ) , buf , len ) ;
2021-02-03 20:10:14 -08:00
}
2017-02-07 00:38:12 +01:00
if ( old . fbo ) {
2022-08-22 21:28:43 +02:00
INFO_LOG ( FRAMEBUF , " Resizing FBO for %08x : %dx%dx%s " , vfb - > fb_address , w , h , GeBufferFormatToString ( vfb - > fb_format ) ) ;
2017-02-07 00:38:12 +01:00
if ( vfb - > fbo ) {
2022-10-23 11:21:35 +02:00
draw_ - > BindFramebufferAsRenderTarget ( vfb - > fbo , { Draw : : RPAction : : CLEAR , Draw : : RPAction : : CLEAR , Draw : : RPAction : : CLEAR } , " ResizeFramebufFBO " ) ;
2019-02-08 15:02:31 +01:00
if ( ! skipCopy ) {
2022-09-22 09:57:53 +02: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 , RASTER_COLOR , " BlitColor_ResizeFramebufFBO " ) ;
}
if ( vfb - > usageFlags & FB_USAGE_RENDER_DEPTH ) {
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 , RASTER_DEPTH , " BlitDepth_ResizeFramebufFBO " ) ;
2017-02-07 00:38:12 +01:00
}
}
2017-10-25 20:28:12 +02:00
fbosToDelete_ . push_back ( old . fbo ) ;
2022-10-23 11:21:35 +02:00
draw_ - > BindFramebufferAsRenderTarget ( vfb - > fbo , { Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP } , " ResizeFramebufFBO " ) ;
2017-05-16 16:00:34 +02:00
} else {
2022-10-23 11:21:35 +02:00
draw_ - > BindFramebufferAsRenderTarget ( vfb - > fbo , { Draw : : RPAction : : CLEAR , Draw : : RPAction : : CLEAR , Draw : : RPAction : : CLEAR } , " ResizeFramebufFBO " ) ;
2017-02-07 00:38:12 +01:00
}
2021-12-08 21:57:32 +01:00
currentRenderVfb_ = vfb ;
2017-02-07 00:38:12 +01:00
if ( ! vfb - > fbo ) {
2020-08-27 10:02:50 +02:00
ERROR_LOG ( FRAMEBUF , " Error creating FBO during resize! %dx%d " , vfb - > renderWidth , vfb - > renderHeight ) ;
2017-04-24 09:30:04 -07:00
vfb - > last_frame_failed = gpuStats . numFlips ;
2017-02-07 00:38:12 +01:00
}
}
2020-05-10 23:09:40 +02:00
// This is called from detected memcopies and framebuffer initialization from VRAM. Not block transfers.
2018-11-11 22:50:15 +01:00
// MotoGP goes this path so we need to catch those copies here.
2022-10-03 20:17:25 -07:00
bool FramebufferManagerCommon : : NotifyFramebufferCopy ( u32 src , u32 dst , int size , GPUCopyFlag flags , u32 skipDrawReason ) {
2017-12-25 11:17:59 -08:00
if ( size = = 0 ) {
2014-09-13 15:40:55 -07:00
return false ;
}
dst & = 0x3FFFFFFF ;
src & = 0x3FFFFFFF ;
2022-10-02 21:28:53 -07:00
if ( Memory : : IsVRAMAddress ( dst ) )
dst & = 0x041FFFFF ;
if ( Memory : : IsVRAMAddress ( src ) )
src & = 0x041FFFFF ;
2014-09-13 15:40:55 -07:00
2022-10-01 23:53:13 +02:00
// TODO: Merge the below into FindTransferFramebuffer.
// Or at least this should be like the other ones, gathering possible candidates
// with the ability to list them out for debugging.
2022-08-25 00:47:19 +02:00
2022-10-03 20:22:27 -07:00
VirtualFramebuffer * dstBuffer = nullptr ;
VirtualFramebuffer * srcBuffer = nullptr ;
bool ignoreDstBuffer = flags & GPUCopyFlag : : FORCE_DST_MEM ;
bool ignoreSrcBuffer = flags & ( GPUCopyFlag : : FORCE_SRC_MEM | GPUCopyFlag : : MEMSET ) ;
2022-10-09 00:50:45 -07:00
RasterChannel channel = flags & GPUCopyFlag : : DEPTH_REQUESTED ? RASTER_DEPTH : RASTER_COLOR ;
2022-10-03 20:22:27 -07:00
2014-09-13 15:40:55 -07:00
u32 dstY = ( u32 ) - 1 ;
u32 dstH = 0 ;
u32 srcY = ( u32 ) - 1 ;
u32 srcH = 0 ;
2022-08-25 00:52:45 +02:00
for ( auto vfb : vfbs_ ) {
2022-10-09 00:50:45 -07:00
if ( vfb - > fb_stride = = 0 | | channel ! = RASTER_COLOR ) {
2014-09-21 21:35:39 -07:00
continue ;
}
2018-11-11 10:54:28 +01:00
// We only remove the kernel and uncached bits when comparing.
2022-10-02 21:28:53 -07:00
const u32 vfb_address = vfb - > fb_address ;
2019-09-17 14:45:40 +02:00
const u32 vfb_size = ColorBufferByteSize ( vfb ) ;
2022-08-22 21:28:43 +02:00
const u32 vfb_bpp = BufferFormatBytesPerPixel ( vfb - > fb_format ) ;
2014-09-13 15:40:55 -07:00
const u32 vfb_byteStride = vfb - > fb_stride * vfb_bpp ;
const int vfb_byteWidth = vfb - > width * vfb_bpp ;
2022-10-01 23:53:13 +02:00
// Heuristic to try to prevent potential glitches with video playback.
2022-10-11 09:55:53 +02:00
if ( ! ignoreDstBuffer & & vfb_address = = dst & & ( ( size = = 0x44000 & & vfb_size = = 0x88000 ) | | ( size = = 0x88000 & & vfb_size = = 0x44000 ) ) ) {
2022-10-01 23:53:13 +02:00
// Not likely to be a correct color format copy for this buffer. Ignore it, there will either be RAM
// that can be displayed from, or another matching buffer with the right format if rendering is going on.
WARN_LOG_N_TIMES ( notify_copy_2x , 5 , G3D , " Framebuffer size %08x conspicuously not matching copy size %08x in NotifyFramebufferCopy. Ignoring. " , size , vfb_size ) ;
continue ;
}
2022-10-03 20:22:27 -07:00
if ( ! ignoreDstBuffer & & dst > = vfb_address & & ( dst + size < = vfb_address + vfb_size | | dst = = vfb_address ) ) {
2014-09-13 15:40:55 -07:00
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 ) ;
}
}
2022-10-03 20:22:27 -07:00
if ( ! ignoreSrcBuffer & & src > = vfb_address & & ( src + size < = vfb_address + vfb_size | | src = = vfb_address ) ) {
2014-09-13 15:40:55 -07:00
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 11:02:12 -07: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 00:08:00 -07: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 15:40:55 -07:00
}
}
}
2022-10-09 00:50:45 -07:00
if ( channel = = RASTER_DEPTH ) {
srcBuffer = nullptr ;
dstBuffer = nullptr ;
// Let's assume exact matches only for simplicity.
for ( auto vfb : vfbs_ ) {
if ( ! ignoreDstBuffer & & dst = = vfb - > z_address & & size = = vfb - > z_stride * 2 * vfb - > height ) {
if ( ! dstBuffer | | dstBuffer - > depthBindSeq < vfb - > depthBindSeq ) {
dstBuffer = vfb ;
dstY = 0 ;
dstH = vfb - > height ;
}
}
if ( ! ignoreSrcBuffer & & src = = vfb - > z_address & & size = = vfb - > z_stride * 2 * vfb - > height ) {
if ( ! srcBuffer | | srcBuffer - > depthBindSeq < vfb - > depthBindSeq ) {
srcBuffer = vfb ;
srcY = 0 ;
srcH = vfb - > height ;
}
}
}
}
2014-09-13 15:40:55 -07:00
if ( ! useBufferedRendering_ ) {
// If we're copying into a recently used display buf, it's probably destined for the screen.
2022-10-09 00:50:45 -07:00
if ( channel = = RASTER_DEPTH | | srcBuffer | | ( dstBuffer ! = displayFramebuf_ & & dstBuffer ! = prevDisplayFramebuf_ ) ) {
2014-09-13 15:40:55 -07:00
return false ;
}
}
2022-10-09 00:50:45 -07:00
if ( ! dstBuffer & & srcBuffer & & channel ! = RASTER_DEPTH ) {
2020-10-10 23:48:37 +02:00
// Note - if we're here, we're in a memcpy, not a block transfer. Not allowing IntraVRAMBlockTransferAllowCreateFB.
// Technically, that makes BlockTransferAllowCreateFB a bit of a misnomer.
if ( PSP_CoreParameter ( ) . compat . flags ( ) . BlockTransferAllowCreateFB ) {
2022-08-22 21:28:43 +02:00
dstBuffer = CreateRAMFramebuffer ( dst , srcBuffer - > width , srcBuffer - > height , srcBuffer - > fb_stride , srcBuffer - > fb_format ) ;
2020-10-10 23:48:37 +02:00
dstY = 0 ;
}
2018-11-11 22:50:15 +01:00
}
if ( dstBuffer ) {
dstBuffer - > last_frame_used = gpuStats . numFlips ;
2022-12-12 23:23:14 -08:00
if ( channel = = RASTER_DEPTH & & ! srcBuffer )
dstBuffer - > usageFlags | = FB_USAGE_COLOR_MIXED_DEPTH ;
2018-11-11 22:50:15 +01:00
}
2022-12-12 23:23:14 -08:00
if ( srcBuffer & & channel = = RASTER_DEPTH & & ! dstBuffer )
srcBuffer - > usageFlags | = FB_USAGE_COLOR_MIXED_DEPTH ;
2018-11-11 22:50:15 +01:00
2022-10-03 20:22:27 -07:00
if ( dstBuffer & & srcBuffer ) {
2014-09-13 15:40:55 -07:00
if ( srcBuffer = = dstBuffer ) {
2020-08-10 09:16:28 +02:00
WARN_LOG_ONCE ( dstsrccpy , G3D , " Intra-buffer memcpy (not supported) %08x -> %08x (size: %x) " , src , dst , size ) ;
2014-09-13 15:40:55 -07:00
} else {
2020-08-10 09:16:28 +02:00
WARN_LOG_ONCE ( dstnotsrccpy , G3D , " Inter-buffer memcpy %08x -> %08x (size: %x) " , src , dst , size ) ;
2014-09-13 15:40:55 -07:00
// Just do the blit!
2022-10-09 00:50:45 -07:00
BlitFramebuffer ( dstBuffer , 0 , dstY , srcBuffer , 0 , srcY , srcBuffer - > width , srcH , 0 , channel , " Blit_InterBufferMemcpy " ) ;
2019-02-08 14:50:47 +01:00
SetColorUpdated ( dstBuffer , skipDrawReason ) ;
2020-06-02 09:51:38 +02:00
RebindFramebuffer ( " RebindFramebuffer - Inter-buffer memcpy " ) ;
2014-09-13 15:40:55 -07:00
}
return false ;
} else if ( dstBuffer ) {
2022-10-03 20:17:25 -07:00
if ( flags & GPUCopyFlag : : MEMSET ) {
2017-12-26 19:34:57 -08:00
gpuStats . numClears + + ;
}
2017-11-22 12:24:05 +01:00
WARN_LOG_ONCE ( btucpy , G3D , " Memcpy fbo upload %08x -> %08x (size: %x) " , src , dst , size ) ;
2019-02-08 14:50:47 +01:00
FlushBeforeCopy ( ) ;
const u8 * srcBase = Memory : : GetPointerUnchecked ( src ) ;
2022-10-09 00:50:45 -07:00
GEBufferFormat srcFormat = channel = = RASTER_DEPTH ? GE_FORMAT_DEPTH16 : dstBuffer - > fb_format ;
int srcStride = channel = = RASTER_DEPTH ? dstBuffer - > z_stride : dstBuffer - > fb_stride ;
DrawPixels ( dstBuffer , 0 , dstY , srcBase , srcFormat , srcStride , dstBuffer - > width , dstH , channel , " MemcpyFboUpload_DrawPixels " ) ;
2019-02-08 14:50:47 +01:00
SetColorUpdated ( dstBuffer , skipDrawReason ) ;
2020-06-02 09:51:38 +02:00
RebindFramebuffer ( " RebindFramebuffer - Memcpy fbo upload " ) ;
2019-02-08 14:50:47 +01:00
// This is a memcpy, let's still copy just in case.
2014-09-13 15:40:55 -07: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 09:16:28 +02:00
WARN_LOG_ONCE ( btdcpyheight , G3D , " Memcpy fbo download %08x -> %08x skipped, %d+%d is taller than %d " , src , dst , srcY , srcH , srcBuffer - > bufferHeight ) ;
2022-11-06 19:30:17 +01:00
} else if ( ! g_Config . bSkipGPUReadbacks & & ( ! srcBuffer - > memoryUpdated | | channel = = RASTER_DEPTH ) ) {
2023-02-05 10:52:52 +01:00
ReadFramebufferToMemory ( srcBuffer , 0 , srcY , srcBuffer - > width , srcH , channel , Draw : : ReadbackMode : : BLOCK ) ;
2017-04-09 15:10:07 -07:00
srcBuffer - > usageFlags = ( srcBuffer - > usageFlags | FB_USAGE_DOWNLOAD ) & ~ FB_USAGE_DOWNLOAD_CLEAR ;
2014-09-13 15:40:55 -07:00
}
return false ;
} else {
return false ;
}
}
2022-08-25 23:08:38 +02:00
std : : string BlockTransferRect : : ToString ( ) const {
int bpp = BufferFormatBytesPerPixel ( vfb - > fb_format ) ;
return StringFromFormat ( " %08x/%d/%s seq:%d %d,%d %dx%d " , vfb - > fb_address , vfb - > FbStrideInBytes ( ) , GeBufferFormatToString ( vfb - > fb_format ) , vfb - > colorBindSeq , x_bytes / bpp , y , w_bytes / bpp , h ) ;
}
2014-09-13 15:40:55 -07:00
2022-08-25 23:08:38 +02:00
// Only looks for color buffers. Due to swizzling and other concerns, games have not been seen using block copies
// for depth data yet.
bool FramebufferManagerCommon : : FindTransferFramebuffer ( u32 basePtr , int stride_pixels , int x_pixels , int y , int w_pixels , int h , int bpp , bool destination , BlockTransferRect * rect ) {
2022-08-25 00:17:01 +02:00
basePtr & = 0x3FFFFFFF ;
2022-10-02 21:28:53 -07:00
if ( Memory : : IsVRAMAddress ( basePtr ) )
basePtr & = 0x041FFFFF ;
2022-08-25 23:08:38 +02:00
rect - > vfb = nullptr ;
if ( ! stride_pixels ) {
WARN_LOG ( G3D , " Zero stride in FindTransferFrameBuffer, ignoring " ) ;
return false ;
}
const u32 byteStride = stride_pixels * bpp ;
int x_bytes = x_pixels * bpp ;
int w_bytes = w_pixels * bpp ;
2022-08-31 14:23:56 +02:00
TinySet < BlockTransferRect , 4 > candidates ;
2022-08-25 23:08:38 +02:00
// We work entirely in bytes when we do the matching, because games don't consistently use bpps that match
// that of their buffers. Then after matching we try to map the copy to the simplest operation that does
// what we need.
2014-09-13 15:40:55 -07:00
2022-09-12 11:16:30 +02:00
// We are only looking at color for now, have not found any block transfers of depth data (although it's plausible).
2022-08-25 00:52:45 +02:00
for ( auto vfb : vfbs_ ) {
2022-09-12 11:16:30 +02:00
// Check for easily detected depth copies for logging purposes.
// Depth copies are not that useful though because you manually need to account for swizzle, so
// not sure if games will use them.
2022-10-02 21:28:53 -07:00
if ( vfb - > z_address = = basePtr ) {
2022-09-12 11:16:30 +02:00
WARN_LOG_N_TIMES ( z_xfer , 5 , G3D , " FindTransferFramebuffer: found matching depth buffer, %08x (dest=%d, bpp=%d) " , basePtr , ( int ) destination , bpp ) ;
}
2022-10-02 21:28:53 -07:00
const u32 vfb_address = vfb - > fb_address ;
2022-08-25 00:12:31 +02:00
const u32 vfb_size = ColorBufferByteSize ( vfb ) ;
2022-08-25 23:08:38 +02:00
2022-08-28 07:31:50 +02:00
if ( basePtr < vfb_address | | basePtr > = vfb_address + vfb_size ) {
2022-08-25 23:08:38 +02:00
continue ;
}
2022-08-25 00:12:31 +02:00
const u32 vfb_bpp = BufferFormatBytesPerPixel ( vfb - > fb_format ) ;
2022-08-25 23:08:38 +02:00
const u32 vfb_byteStride = vfb - > FbStrideInBytes ( ) ;
const u32 vfb_byteWidth = vfb - > WidthInBytes ( ) ;
BlockTransferRect candidate { vfb } ;
candidate . w_bytes = w_pixels * bpp ;
candidate . h = h ;
const u32 byteOffset = basePtr - vfb_address ;
const int memXOffset = byteOffset % byteStride ;
const int memYOffset = byteOffset / byteStride ;
// 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.
// Use bufferHeight in case of buffers that resize up and down often per frame (Valkyrie Profile.)
// If it's outside the vfb by a single pixel, we currently disregard it.
if ( memYOffset > vfb - > bufferHeight - h ) {
continue ;
}
2022-08-28 07:31:50 +02:00
if ( byteOffset = = vfb - > WidthInBytes ( ) & & w_bytes < vfb - > FbStrideInBytes ( ) ) {
// Looks like we're in a margin texture of the vfb, which is not the vfb itself.
2022-08-25 23:08:38 +02:00
// Ignore the match.
continue ;
}
if ( vfb_byteStride ! = byteStride ) {
2022-08-28 07:31:50 +02:00
// Grand Knights History occasionally copies with a mismatching stride but a full line at a time.
2022-08-25 23:08:38 +02:00
// That's why we multiply by height, not width - this copy is a rectangle with the wrong stride but a line with the correct one.
// Makes it hard to detect the wrong transfers in e.g. God of War.
if ( w_pixels ! = stride_pixels | | ( byteStride * h ! = vfb_byteStride & & byteStride * h ! = vfb_byteWidth ) ) {
if ( destination ) {
// However, some other games write cluts to framebuffers.
// Let's catch this and upload. Otherwise reject the match.
bool match = ( vfb - > usageFlags & FB_USAGE_CLUT ) ! = 0 ;
if ( match ) {
candidate . w_bytes = byteStride * h ;
h = 1 ;
2022-08-25 00:22:33 +02:00
} else {
2022-08-25 23:08:38 +02:00
continue ;
2015-04-28 00:08:00 -07:00
}
2014-09-13 15:40:55 -07:00
} else {
2022-08-25 23:08:38 +02:00
continue ;
2014-09-13 15:40:55 -07:00
}
2022-08-25 23:08:38 +02:00
} else {
// This is the Grand Knights History case.
candidate . w_bytes = byteStride * h ;
candidate . h = 1 ;
2014-09-13 15:40:55 -07:00
}
2022-08-25 23:08:38 +02:00
} else {
candidate . w_bytes = w_bytes ;
candidate . h = h ;
}
candidate . x_bytes = x_bytes + memXOffset ;
candidate . y = y + memYOffset ;
candidate . vfb = vfb ;
candidates . push_back ( candidate ) ;
}
2022-08-31 14:23:56 +02:00
const BlockTransferRect * best = nullptr ;
2022-08-25 23:08:38 +02:00
// Sort candidates by just recency for now, we might add other.
2022-08-31 14:23:56 +02:00
for ( size_t i = 0 ; i < candidates . size ( ) ; i + + ) {
const BlockTransferRect * candidate = & candidates [ i ] ;
2022-09-12 11:16:30 +02:00
bool better = ! best | | candidate - > vfb - > colorBindSeq > best - > vfb - > colorBindSeq ;
if ( ( candidate - > vfb - > usageFlags & FB_USAGE_CLUT ) & & candidate - > x_bytes = = 0 & & candidate - > y = = 0 & & destination ) {
// Hack to prioritize copies to clut buffers.
best = candidate ;
break ;
}
if ( better ) {
2022-08-31 14:23:56 +02:00
best = candidate ;
}
}
2022-08-25 23:08:38 +02:00
if ( candidates . size ( ) > 1 ) {
2022-08-31 14:23:56 +02:00
if ( Reporting : : ShouldLogNTimes ( " mulblock " , 5 ) ) {
std : : string log ;
for ( size_t i = 0 ; i < candidates . size ( ) ; i + + ) {
log + = " - " + candidates [ i ] . ToString ( ) + " \n " ;
}
WARN_LOG ( G3D , " Multiple framebuffer candidates for %08x/%d/%d %d,%d %dx%d (dest = %d): \n %s " , basePtr , stride_pixels , bpp , x_pixels , y , w_pixels , h , ( int ) destination , log . c_str ( ) ) ;
2014-09-13 15:40:55 -07:00
}
2018-11-05 00:28:01 +01:00
}
2018-11-11 22:50:15 +01:00
2022-12-10 21:12:36 -08:00
if ( best ) {
2022-08-31 14:23:56 +02:00
* rect = * best ;
2022-08-25 23:08:38 +02:00
return true ;
} else {
2022-08-27 13:57:36 +02:00
if ( Memory : : IsVRAMAddress ( basePtr ) & & destination & & h > = 128 ) {
WARN_LOG_N_TIMES ( nocands , 5 , G3D , " Didn't find a destination candidate for %08x/%d/%d %d,%d %dx%d " , basePtr , stride_pixels , bpp , x_pixels , y , w_pixels , h ) ;
2022-08-26 23:26:44 +02:00
}
2022-08-25 23:08:38 +02:00
return false ;
2014-09-13 15:40:55 -07:00
}
}
2018-11-11 22:50:15 +01:00
VirtualFramebuffer * FramebufferManagerCommon : : CreateRAMFramebuffer ( uint32_t fbAddress , int width , int height , int stride , GEBufferFormat format ) {
2022-08-22 21:28:43 +02:00
INFO_LOG ( G3D , " Creating RAM framebuffer at %08x (%dx%d, stride %d, fb_format %d) " , fbAddress , width , height , stride , format ) ;
2019-09-24 23:10:18 +02:00
2018-11-11 22:50:15 +01: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 ;
2022-10-02 21:28:53 -07:00
uint32_t mask = Memory : : IsVRAMAddress ( fbAddress ) ? 0x041FFFFF : 0x3FFFFFFF ;
vfb - > fb_address = fbAddress & mask ; // NOTE - not necessarily in VRAM!
2018-11-11 22:50:15 +01:00
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 ;
2020-11-06 21:40:31 +01:00
vfb - > renderScaleFactor = renderScaleFactor_ ;
vfb - > renderWidth = ( u16 ) ( vfb - > width * renderScaleFactor_ ) ;
vfb - > renderHeight = ( u16 ) ( vfb - > height * renderScaleFactor_ ) ;
2018-11-11 22:50:15 +01:00
vfb - > bufferWidth = vfb - > width ;
vfb - > bufferHeight = vfb - > height ;
2022-08-22 21:28:43 +02:00
vfb - > fb_format = format ;
2022-08-17 19:55:19 +02:00
vfb - > usageFlags = FB_USAGE_RENDER_COLOR ;
2018-11-11 22:50:15 +01:00
SetColorUpdated ( vfb , 0 ) ;
2020-08-09 09:35:56 +02:00
char name [ 64 ] ;
snprintf ( name , sizeof ( name ) , " %08x_color_RAM " , vfb - > fb_address ) ;
2020-09-20 21:46:40 +02:00
textureCache_ - > NotifyFramebuffer ( vfb , NOTIFY_FB_CREATED ) ;
2022-11-28 18:20:30 +01:00
vfb - > fbo = draw_ - > CreateFramebuffer ( { vfb - > renderWidth , vfb - > renderHeight , 1 , GetFramebufferLayers ( ) , 0 , true , name } ) ;
2018-11-11 22:50:15 +01:00
vfbs_ . push_back ( vfb ) ;
2019-09-17 14:45:40 +02:00
2019-09-28 08:40:41 -07:00
u32 byteSize = ColorBufferByteSize ( vfb ) ;
2019-09-17 14:45:40 +02:00
if ( fbAddress + byteSize > framebufRangeEnd_ ) {
framebufRangeEnd_ = fbAddress + byteSize ;
}
2018-11-11 22:50:15 +01:00
return vfb ;
}
2023-02-04 12:05:50 +01:00
// 1:1 pixel size buffers, we resize buffers to these before we read them back.
// TODO: We shouldn't keep whole VirtualFramebuffer structs for these - the fbo and last_frame_render is enough.
2022-08-29 23:59:43 +02:00
VirtualFramebuffer * FramebufferManagerCommon : : FindDownloadTempBuffer ( VirtualFramebuffer * vfb , RasterChannel channel ) {
2016-01-04 20:40:07 -08:00
// 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 16:55:21 -07:00
VirtualFramebuffer * nvfb = nullptr ;
2016-01-04 20:40:07 -08:00
// We maintain a separate vector of framebuffer objects for blitting.
2020-05-16 16:55:21 -07:00
for ( VirtualFramebuffer * v : bvfbs_ ) {
2023-02-04 12:05:50 +01:00
if ( v - > Address ( channel ) = = vfb - > Address ( channel ) & & v - > Format ( channel ) = = vfb - > Format ( channel ) ) {
2016-01-04 20:40:07 -08:00
if ( v - > bufferWidth = = vfb - > bufferWidth & & v - > bufferHeight = = vfb - > bufferHeight ) {
nvfb = v ;
2023-02-04 12:05:50 +01:00
if ( channel = = RASTER_COLOR ) {
v - > fb_stride = vfb - > fb_stride ;
} else {
v - > z_stride = vfb - > z_stride ;
}
2016-01-04 20:40:07 -08:00
v - > width = vfb - > width ;
v - > height = vfb - > height ;
break ;
}
}
}
// Create a new fbo if none was found for the size
if ( ! nvfb ) {
2022-04-24 20:53:09 +02:00
nvfb = new VirtualFramebuffer { } ;
2016-01-04 20:40:07 -08:00
nvfb - > fbo = nullptr ;
2023-02-04 12:05:50 +01:00
nvfb - > fb_address = channel = = RASTER_COLOR ? vfb - > fb_address : 0 ;
nvfb - > fb_stride = channel = = RASTER_COLOR ? vfb - > fb_stride : 0 ;
nvfb - > z_address = channel = = RASTER_DEPTH ? vfb - > z_address : 0 ;
nvfb - > z_stride = channel = = RASTER_DEPTH ? vfb - > z_stride : 0 ;
2016-01-04 20:40:07 -08:00
nvfb - > width = vfb - > width ;
nvfb - > height = vfb - > height ;
nvfb - > renderWidth = vfb - > bufferWidth ;
nvfb - > renderHeight = vfb - > bufferHeight ;
2022-08-16 22:39:09 +02:00
nvfb - > renderScaleFactor = 1 ; // For readbacks we resize to the original size, of course.
2016-01-04 20:40:07 -08:00
nvfb - > bufferWidth = vfb - > bufferWidth ;
nvfb - > bufferHeight = vfb - > bufferHeight ;
2022-08-22 21:28:43 +02:00
nvfb - > fb_format = vfb - > fb_format ;
2016-01-04 20:40:07 -08:00
nvfb - > drawnWidth = vfb - > drawnWidth ;
nvfb - > drawnHeight = vfb - > drawnHeight ;
2016-01-04 20:51:43 -08:00
2020-11-05 14:38:20 +01:00
char name [ 64 ] ;
2023-02-04 12:05:50 +01:00
snprintf ( name , sizeof ( name ) , " download_temp_%08x_%s " , vfb - > Address ( channel ) , RasterChannelToString ( channel ) ) ;
// We always create a color-only framebuffer here - readbacks of depth convert to color while translating the values.
nvfb - > fbo = draw_ - > CreateFramebuffer ( { nvfb - > bufferWidth , nvfb - > bufferHeight , 1 , 1 , 0 , false , name } ) ;
2020-11-05 14:38:20 +01:00
if ( ! nvfb - > fbo ) {
2020-11-06 09:09:18 +01:00
ERROR_LOG ( FRAMEBUF , " Error creating FBO! %d x %d " , nvfb - > renderWidth , nvfb - > renderHeight ) ;
2022-09-30 12:32:49 +03:00
delete nvfb ;
2020-11-05 18:25:14 +01:00
return nullptr ;
2016-01-04 20:51:43 -08:00
}
bvfbs_ . push_back ( nvfb ) ;
2020-11-06 09:09:18 +01:00
} else {
UpdateDownloadTempBuffer ( nvfb ) ;
2016-01-04 20:40:07 -08:00
}
2022-08-17 19:55:19 +02:00
nvfb - > usageFlags | = FB_USAGE_RENDER_COLOR ;
2016-01-04 20:40:07 -08:00
nvfb - > last_frame_render = gpuStats . numFlips ;
nvfb - > dirtyAfterDisplay = true ;
return nvfb ;
}
2017-04-09 15:10:07 -07: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 ;
}
}
2022-04-24 14:56:48 +02:00
2021-01-31 12:16:30 -08:00
if ( ! Memory : : IsValidAddress ( gstate . getFrameBufAddress ( ) ) ) {
return ;
}
2017-04-09 15:10:07 -07:00
2022-07-20 12:40:22 +02:00
u8 * addr = Memory : : GetPointerWriteUnchecked ( gstate . getFrameBufAddress ( ) ) ;
2022-08-18 10:51:50 +02:00
const int bpp = BufferFormatBytesPerPixel ( gstate_c . framebufFormat ) ;
2017-04-09 15:12:56 -07:00
u32 clearBits = clearColor ;
if ( bpp = = 2 ) {
u16 clear16 = 0 ;
2022-04-24 17:30:33 +02:00
switch ( gstate_c . framebufFormat ) {
2022-04-24 14:55:51 +02:00
case GE_FORMAT_565 : clear16 = RGBA8888toRGB565 ( clearColor ) ; break ;
case GE_FORMAT_5551 : clear16 = RGBA8888toRGBA5551 ( clearColor ) ; break ;
case GE_FORMAT_4444 : clear16 = RGBA8888toRGBA4444 ( clearColor ) ; break ;
2020-07-19 17:47:02 +02:00
default : _dbg_assert_ ( 0 ) ; break ;
2017-04-09 15:12:56 -07:00
}
clearBits = clear16 | ( clear16 < < 16 ) ;
}
const bool singleByteClear = ( clearBits > > 16 ) = = ( clearBits & 0xFFFF ) & & ( clearBits > > 24 ) = = ( clearBits & 0xFF ) ;
2017-04-09 15:10:07 -07:00
const int stride = gstate . FrameBufStride ( ) ;
const int width = x2 - x1 ;
2021-02-02 00:08:05 -08:00
const int byteStride = stride * bpp ;
const int byteWidth = width * bpp ;
for ( int y = y1 ; y < y2 ; + + y ) {
NotifyMemInfo ( MemBlockFlags : : WRITE , gstate . getFrameBufAddress ( ) + x1 * bpp + y * byteStride , byteWidth , " FramebufferClear " ) ;
}
2017-04-09 15:10:07 -07:00
// Can use memset for simple cases. Often alpha is different and gums up the works.
2017-04-09 15:12:56 -07:00
if ( singleByteClear ) {
2017-04-09 15:10:07 -07:00
addr + = x1 * bpp ;
for ( int y = y1 ; y < y2 ; + + y ) {
2017-04-09 15:12:56 -07:00
memset ( addr + y * byteStride , clearBits , byteWidth ) ;
2017-04-09 15:10:07 -07: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 15:12:56 -07:00
u64 val64 = clearBits | ( ( u64 ) clearBits < < 32 ) ;
int xstride = 8 / bpp ;
2017-04-09 15:10:07 -07: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 15:12:56 -07:00
addr32 [ y * stride + x ] = clearBits ;
2017-04-09 15:10:07 -07: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 15:12:56 -07:00
addr16 [ y * stride + x ] = ( u16 ) clearBits ;
2017-04-09 15:10:07 -07: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 15:19:06 -07:00
currentRenderVfb_ - > memoryUpdated = true ;
2017-04-09 15:10:07 -07:00
}
}
}
2015-08-05 12:13:14 +02: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 11:17:59 -08:00
if ( ! useBufferedRendering_ ) {
2014-09-13 15:40:55 -07:00
return false ;
}
// Skip checking if there's no framebuffers in that area.
if ( ! MayIntersectFramebuffer ( srcBasePtr ) & & ! MayIntersectFramebuffer ( dstBasePtr ) ) {
return false ;
}
2022-08-25 23:08:38 +02:00
BlockTransferRect dstRect { } ;
BlockTransferRect srcRect { } ;
2022-06-11 11:58:58 +02:00
2022-08-25 00:25:53 +02:00
// These modify the X/Y/W/H parameters depending on the memory offset of the base pointers from the actual buffers.
2022-08-25 23:08:38 +02:00
bool srcBuffer = FindTransferFramebuffer ( srcBasePtr , srcStride , srcX , srcY , width , height , bpp , false , & srcRect ) ;
bool dstBuffer = FindTransferFramebuffer ( dstBasePtr , dstStride , dstX , dstY , width , height , bpp , true , & dstRect ) ;
2022-08-25 00:12:31 +02:00
if ( srcBuffer & & ! dstBuffer ) {
2022-08-25 23:08:38 +02:00
// In here, we can't read from dstRect.
2022-08-25 00:12:31 +02:00
if ( PSP_CoreParameter ( ) . compat . flags ( ) . BlockTransferAllowCreateFB | |
( PSP_CoreParameter ( ) . compat . flags ( ) . IntraVRAMBlockTransferAllowCreateFB & &
2022-08-25 23:08:38 +02:00
Memory : : IsVRAMAddress ( srcRect . vfb - > fb_address ) & & Memory : : IsVRAMAddress ( dstBasePtr ) ) ) {
2022-08-25 00:12:31 +02: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 ;
2022-08-25 23:08:38 +02:00
} else if ( srcRect . vfb - > fb_format ! = GE_FORMAT_8888 ) {
2022-08-25 00:12:31 +02:00
// 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.
2022-08-25 23:08:38 +02:00
ramFormat = srcRect . vfb - > fb_format ;
2022-08-25 00:12:31 +02:00
} else {
// No info left - just fall back to something. But this is definitely split pixel tricks.
ramFormat = GE_FORMAT_5551 ;
}
2022-08-26 23:26:44 +02:00
dstBuffer = true ;
dstRect . vfb = CreateRAMFramebuffer ( dstBasePtr , width , height , dstStride , ramFormat ) ;
2022-08-25 00:12:31 +02:00
}
}
2022-08-25 23:08:38 +02:00
if ( dstBuffer ) {
dstRect . vfb - > last_frame_used = gpuStats . numFlips ;
// Mark the destination as fresh.
dstRect . vfb - > colorBindSeq = GetBindSeqCount ( ) ;
}
2014-09-13 15:40:55 -07:00
if ( dstBuffer & & srcBuffer ) {
2022-08-25 23:08:38 +02:00
if ( srcRect . vfb = = dstRect . vfb ) {
// Transfer within the same buffer.
// This is a simple case because there will be no format conversion or similar shenanigans needed.
// However, the BPP might still mismatch, but in such a case we can convert the coordinates.
if ( srcX = = dstX & & srcY = = dstY ) {
2014-09-13 15:40:55 -07:00
// Ignore, nothing to do. Tales of Phantasia X does this by accident.
2022-08-25 23:08:38 +02:00
// Returning true to also skip the memory copy.
return true ;
2014-09-13 15:40:55 -07:00
}
2022-08-25 23:08:38 +02:00
int buffer_bpp = BufferFormatBytesPerPixel ( srcRect . vfb - > fb_format ) ;
if ( bpp ! = buffer_bpp ) {
WARN_LOG_ONCE ( intrabpp , G3D , " Mismatched transfer bpp in intra-buffer block transfer. Was %d, expected %d. " , bpp , buffer_bpp ) ;
// We just switch to using the buffer's bpp, since we've already converted the rectangle to byte offsets.
bpp = buffer_bpp ;
}
2022-08-27 13:57:36 +02:00
WARN_LOG_N_TIMES ( dstsrc , 5 , G3D , " Intra-buffer block transfer %dx%d %dbpp from %08x (x:%d y:%d stride:%d) -> %08x (x:%d y:%d stride:%d) " ,
2022-08-25 23:08:38 +02:00
width , height , bpp ,
srcBasePtr , srcRect . x_bytes / bpp , srcRect . y , srcStride ,
dstBasePtr , dstRect . x_bytes / bpp , dstRect . y , dstStride ) ;
FlushBeforeCopy ( ) ;
// Some backends can handle blitting within a framebuffer. Others will just have to deal with it or ignore it, apparently.
2022-08-29 23:59:43 +02:00
BlitFramebuffer ( dstRect . vfb , dstX , dstY , srcRect . vfb , srcX , srcY , dstRect . w_bytes / bpp , dstRect . h / bpp , bpp , RASTER_COLOR , " Blit_IntraBufferBlockTransfer " ) ;
2022-08-25 23:08:38 +02:00
RebindFramebuffer ( " rebind after intra block transfer " ) ;
SetColorUpdated ( dstRect . vfb , skipDrawReason ) ;
return true ; // Skip the memory copy.
}
2022-08-27 19:24:25 +02:00
// Straightforward blit between two same-format framebuffers.
2022-08-25 23:08:38 +02:00
if ( srcRect . vfb - > fb_format = = dstRect . vfb - > fb_format ) {
2022-08-27 13:57:36 +02:00
WARN_LOG_N_TIMES ( dstnotsrc , 5 , G3D , " Inter-buffer block transfer %dx%d %dbpp from %08x (x:%d y:%d stride:%d %s) -> %08x (x:%d y:%d stride:%d %s) " ,
2020-12-19 19:34:43 +01:00
width , height , bpp ,
2022-08-25 23:08:38 +02:00
srcBasePtr , srcRect . x_bytes / bpp , srcRect . y , srcStride , GeBufferFormatToString ( srcRect . vfb - > fb_format ) ,
2022-08-27 23:20:26 +02:00
dstBasePtr , dstRect . x_bytes / bpp , dstRect . y , dstStride , GeBufferFormatToString ( dstRect . vfb - > fb_format ) ) ;
2022-08-25 23:08:38 +02:00
// Straight blit will do, but check the bpp, we might need to convert coordinates differently.
int buffer_bpp = BufferFormatBytesPerPixel ( srcRect . vfb - > fb_format ) ;
if ( bpp ! = buffer_bpp ) {
WARN_LOG_ONCE ( intrabpp , G3D , " Mismatched transfer bpp in inter-buffer block transfer. Was %d, expected %d. " , bpp , buffer_bpp ) ;
// We just switch to using the buffer's bpp, since we've already converted the rectangle to byte offsets.
bpp = buffer_bpp ;
}
2019-02-08 14:50:47 +01:00
FlushBeforeCopy ( ) ;
2022-08-29 23:59:43 +02:00
BlitFramebuffer ( dstRect . vfb , dstRect . x_bytes / bpp , dstRect . y , srcRect . vfb , srcRect . x_bytes / bpp , srcRect . y , srcRect . w_bytes / bpp , height , bpp , RASTER_COLOR , " Blit_InterBufferBlockTransfer " ) ;
2020-06-02 09:51:38 +02:00
RebindFramebuffer ( " RebindFramebuffer - Inter-buffer block transfer " ) ;
2022-08-25 23:08:38 +02:00
SetColorUpdated ( dstRect . vfb , skipDrawReason ) ;
return true ;
2014-09-13 15:40:55 -07:00
}
2022-08-25 23:08:38 +02:00
2022-08-27 19:24:25 +02:00
// Getting to the more complex cases. Have not actually seen much of these yet.
2022-08-27 13:57:36 +02:00
WARN_LOG_N_TIMES ( blockformat , 5 , G3D , " Mismatched buffer formats in block transfer: %s->%s (%dx%d) " ,
2022-08-25 23:08:38 +02:00
GeBufferFormatToString ( srcRect . vfb - > fb_format ) , GeBufferFormatToString ( dstRect . vfb - > fb_format ) ,
width , height ) ;
2022-08-27 19:24:25 +02:00
// TODO
// No need to actually do the memory copy behind, probably.
return true ;
2022-08-25 23:08:38 +02:00
2014-09-13 15:40:55 -07:00
} else if ( dstBuffer ) {
// Here we should just draw the pixels into the buffer. Copy first.
return false ;
} else if ( srcBuffer ) {
2022-08-27 14:40:42 +02:00
WARN_LOG_N_TIMES ( btd , 10 , G3D , " Block transfer readback %dx%d %dbpp from %08x (x:%d y:%d stride:%d) -> %08x (x:%d y:%d stride:%d) " ,
2020-12-19 19:34:43 +01:00
width , height , bpp ,
2022-08-25 23:08:38 +02:00
srcBasePtr , srcRect . x_bytes / bpp , srcRect . y , srcStride ,
dstBasePtr , dstRect . x_bytes / bpp , dstRect . y , dstStride ) ;
2014-09-13 15:40:55 -07:00
FlushBeforeCopy ( ) ;
2022-11-06 19:30:17 +01:00
if ( ! g_Config . bSkipGPUReadbacks & & ! srcRect . vfb - > memoryUpdated ) {
2022-08-25 23:08:38 +02:00
const int srcBpp = BufferFormatBytesPerPixel ( srcRect . vfb - > fb_format ) ;
2014-09-13 15:40:55 -07:00
const float srcXFactor = ( float ) bpp / srcBpp ;
2022-08-25 23:08:38 +02:00
const bool tooTall = srcY + srcRect . h > srcRect . vfb - > bufferHeight ;
if ( srcRect . h < = 0 | | ( tooTall & & srcY ! = 0 ) ) {
WARN_LOG_ONCE ( btdheight , G3D , " Block transfer download %08x -> %08x skipped, %d+%d is taller than %d " , srcBasePtr , dstBasePtr , srcRect . y , srcRect . h , srcRect . vfb - > bufferHeight ) ;
2014-09-13 15:40:55 -07:00
} else {
2020-08-16 22:22:58 +02:00
if ( tooTall ) {
2022-08-25 23:08:38 +02:00
WARN_LOG_ONCE ( btdheight , G3D , " Block transfer download %08x -> %08x dangerous, %d+%d is taller than %d " , srcBasePtr , dstBasePtr , srcRect . y , srcRect . h , srcRect . vfb - > bufferHeight ) ;
2020-08-16 22:22:58 +02:00
}
2023-02-05 10:52:52 +01:00
ReadFramebufferToMemory ( srcRect . vfb , static_cast < int > ( srcX * srcXFactor ) , srcY , static_cast < int > ( srcRect . w_bytes * srcXFactor ) , srcRect . h , RASTER_COLOR , Draw : : ReadbackMode : : BLOCK ) ;
2022-08-25 23:08:38 +02:00
srcRect . vfb - > usageFlags = ( srcRect . vfb - > usageFlags | FB_USAGE_DOWNLOAD ) & ~ FB_USAGE_DOWNLOAD_CLEAR ;
2014-09-13 15:40:55 -07:00
}
}
return false ; // Let the bit copy happen
} else {
return false ;
}
}
2015-08-05 12:13:14 +02: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-11 19:11:43 -07: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 ;
2022-09-20 14:01:36 -07:00
bool isDisplayBuffer = CurrentDisplayFramebufAddr ( ) = = dstBasePtr ;
2020-05-11 19:11:43 -07:00
if ( isPrevDisplayBuffer | | isDisplayBuffer ) {
FlushBeforeCopy ( ) ;
2022-08-29 10:14:29 +02:00
DrawFramebufferToOutput ( Memory : : GetPointerUnchecked ( dstBasePtr ) , dstStride , displayFormat_ ) ;
2020-05-11 19:11:43 -07:00
return ;
}
2014-09-13 15:40:55 -07:00
}
if ( MayIntersectFramebuffer ( srcBasePtr ) | | MayIntersectFramebuffer ( dstBasePtr ) ) {
2022-08-25 00:12:31 +02:00
// TODO: Figure out how we can avoid repeating the search here.
2022-08-25 23:08:38 +02:00
BlockTransferRect dstRect { } ;
BlockTransferRect srcRect { } ;
// These modify the X/Y/W/H parameters depending on the memory offset of the base pointers from the actual buffers.
bool srcBuffer = FindTransferFramebuffer ( srcBasePtr , srcStride , srcX , srcY , width , height , bpp , false , & srcRect ) ;
bool dstBuffer = FindTransferFramebuffer ( dstBasePtr , dstStride , dstX , dstY , width , height , bpp , true , & dstRect ) ;
2014-09-13 15:40:55 -07:00
2020-05-11 19:11:43 -07: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.
2022-08-25 23:08:38 +02:00
if ( ! useBufferedRendering_ & & currentRenderVfb_ ! = dstRect . vfb ) {
2014-09-13 15:40:55 -07:00
return ;
}
if ( dstBuffer & & ! srcBuffer ) {
2022-09-14 23:13:46 +02:00
WARN_LOG_ONCE ( btu , G3D , " Block transfer upload %08x -> %08x (%dx%d %d,%d bpp=%d) " , srcBasePtr , dstBasePtr , width , height , dstX , dstY , bpp ) ;
2019-02-08 14:50:47 +01:00
FlushBeforeCopy ( ) ;
const u8 * srcBase = Memory : : GetPointerUnchecked ( srcBasePtr ) + ( srcX + srcY * srcStride ) * bpp ;
2022-09-12 11:16:30 +02:00
2022-08-25 23:08:38 +02:00
int dstBpp = BufferFormatBytesPerPixel ( dstRect . vfb - > fb_format ) ;
2019-02-08 14:50:47 +01:00
float dstXFactor = ( float ) bpp / dstBpp ;
2022-08-25 23:08:38 +02:00
if ( dstRect . w_bytes / bpp > dstRect . vfb - > width | | dstRect . h > dstRect . vfb - > height ) {
2022-09-22 09:57:53 +02:00
// The buffer isn't big enough, and we have a clear hint of size. Resize.
2019-02-08 14:50:47 +01:00
// This happens in Valkyrie Profile when uploading video at the ending.
2022-09-22 09:57:53 +02:00
// Also happens to the CLUT framebuffer in the Burnout Dominator lens flare effect. See #16075
2022-08-25 23:08:38 +02:00
ResizeFramebufFBO ( dstRect . vfb , dstRect . w_bytes / bpp , dstRect . h , false , true ) ;
2019-02-08 14:50:47 +01:00
// Make sure we don't flop back and forth.
2022-08-25 23:08:38 +02:00
dstRect . vfb - > newWidth = std : : max ( dstRect . w_bytes / bpp , ( int ) dstRect . vfb - > width ) ;
dstRect . vfb - > newHeight = std : : max ( dstRect . h , ( int ) dstRect . vfb - > height ) ;
dstRect . vfb - > lastFrameNewSize = gpuStats . numFlips ;
2019-02-08 14:50:47 +01:00
// Resizing may change the viewport/etc.
gstate_c . Dirty ( DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE ) ;
2014-09-13 15:40:55 -07:00
}
2022-09-12 11:16:30 +02:00
DrawPixels ( dstRect . vfb , static_cast < int > ( dstX * dstXFactor ) , dstY , srcBase , dstRect . vfb - > fb_format , static_cast < int > ( srcStride * dstXFactor ) , static_cast < int > ( dstRect . w_bytes / bpp * dstXFactor ) , dstRect . h , RASTER_COLOR , " BlockTransferCopy_DrawPixels " ) ;
2022-08-25 23:08:38 +02:00
SetColorUpdated ( dstRect . vfb , skipDrawReason ) ;
2020-06-02 09:51:38 +02:00
RebindFramebuffer ( " RebindFramebuffer - NotifyBlockTransferAfter " ) ;
2014-09-13 15:40:55 -07:00
}
}
}
2015-01-23 10:40:49 +01:00
2016-05-19 20:55:34 -07:00
void FramebufferManagerCommon : : SetSafeSize ( u16 w , u16 h ) {
VirtualFramebuffer * vfb = currentRenderVfb_ ;
if ( vfb ) {
2020-05-23 00:12:22 -07: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-19 20:55:34 -07:00
}
}
2022-11-21 14:54:48 +01:00
void FramebufferManagerCommon : : NotifyDisplayResized ( ) {
pixelWidth_ = PSP_CoreParameter ( ) . pixelWidth ;
pixelHeight_ = PSP_CoreParameter ( ) . pixelHeight ;
presentation_ - > UpdateDisplaySize ( pixelWidth_ , pixelHeight_ ) ;
// No drawing is allowed here. This includes anything that might potentially touch a command buffer, like creating images!
// So we need to defer the post processing initialization.
updatePostShaders_ = true ;
}
2022-12-14 15:15:31 +01:00
void FramebufferManagerCommon : : NotifyRenderResized ( int msaaLevel ) {
2017-04-24 11:59:12 -07:00
gstate_c . skipDrawReason & = ~ SKIPDRAW_NON_DISPLAYED_FB ;
2020-11-05 11:21:00 +01:00
int w , h , scaleFactor ;
presentation_ - > CalculateRenderResolution ( & w , & h , & scaleFactor , & postShaderIsUpscalingFilter_ , & postShaderIsSupersampling_ ) ;
2020-05-16 00:31:14 -07:00
PSP_CoreParameter ( ) . renderWidth = w ;
PSP_CoreParameter ( ) . renderHeight = h ;
2020-11-05 11:21:00 +01:00
PSP_CoreParameter ( ) . renderScaleFactor = scaleFactor ;
2020-05-16 00:31:14 -07:00
2022-12-14 15:15:31 +01:00
if ( UpdateRenderSize ( msaaLevel ) ) {
2020-05-13 00:06:13 -07:00
DestroyAllFBOs ( ) ;
}
2022-10-21 12:52:21 +02:00
// No drawing is allowed here. This includes anything that might potentially touch a command buffer, like creating images!
// So we need to defer the post processing initialization.
updatePostShaders_ = true ;
2020-05-13 00:06:13 -07:00
2017-04-24 11:58:16 -07:00
# ifdef _WIN32
// Seems related - if you're ok with numbers all the time, show some more :)
2023-01-27 19:12:41 +03:00
if ( g_Config . iShowStatusFlags ! = 0 ) {
2017-04-24 11:58:16 -07:00
ShowScreenResolution ( ) ;
}
# endif
}
2022-11-22 23:29:50 +01:00
void FramebufferManagerCommon : : NotifyConfigChanged ( ) {
updatePostShaders_ = true ;
}
2020-05-13 00:06:13 -07:00
void FramebufferManagerCommon : : DestroyAllFBOs ( ) {
currentRenderVfb_ = nullptr ;
displayFramebuf_ = nullptr ;
prevDisplayFramebuf_ = nullptr ;
prevPrevDisplayFramebuf_ = nullptr ;
for ( VirtualFramebuffer * vfb : vfbs_ ) {
2022-08-22 21:28:43 +02:00
INFO_LOG ( FRAMEBUF , " Destroying FBO for %08x : %i x %i x %i " , vfb - > fb_address , vfb - > width , vfb - > height , vfb - > fb_format ) ;
2020-05-13 00:06:13 -07:00
DestroyFramebuf ( vfb ) ;
}
vfbs_ . clear ( ) ;
for ( VirtualFramebuffer * vfb : bvfbs_ ) {
DestroyFramebuf ( vfb ) ;
}
bvfbs_ . clear ( ) ;
for ( auto & tempFB : tempFBOs_ ) {
tempFB . second . fbo - > Release ( ) ;
}
tempFBOs_ . clear ( ) ;
2020-11-05 11:21:00 +01:00
for ( auto iter : fbosToDelete_ ) {
iter - > Release ( ) ;
}
fbosToDelete_ . clear ( ) ;
2020-05-13 00:06:13 -07:00
}
2022-10-23 11:21:35 +02:00
static const char * TempFBOReasonToString ( TempFBO reason ) {
switch ( reason ) {
case TempFBO : : DEPAL : return " depal " ;
case TempFBO : : BLIT : return " blit " ;
case TempFBO : : COPY : return " copy " ;
case TempFBO : : STENCIL : return " stencil " ;
default : break ;
}
return " " ;
}
2020-11-05 14:51:46 +01:00
Draw : : Framebuffer * FramebufferManagerCommon : : GetTempFBO ( TempFBO reason , u16 w , u16 h ) {
u64 key = ( ( u64 ) reason < < 48 ) | ( ( u32 ) w < < 16 ) | h ;
2017-02-07 00:24:38 +01:00
auto it = tempFBOs_ . find ( key ) ;
if ( it ! = tempFBOs_ . end ( ) ) {
it - > second . last_frame_used = gpuStats . numFlips ;
return it - > second . fbo ;
}
2019-03-10 08:35:31 -07:00
bool z_stencil = reason = = TempFBO : : STENCIL ;
2020-08-27 16:51:39 +02:00
char name [ 128 ] ;
2022-10-23 11:21:35 +02:00
snprintf ( name , sizeof ( name ) , " tempfbo_%s_%dx%d " , TempFBOReasonToString ( reason ) , w / renderScaleFactor_ , h / renderScaleFactor_ ) ;
2022-10-18 00:26:10 +02:00
2022-11-28 18:20:30 +01:00
Draw : : Framebuffer * fbo = draw_ - > CreateFramebuffer ( { w , h , 1 , GetFramebufferLayers ( ) , 0 , z_stencil , name } ) ;
2020-08-27 16:51:39 +02:00
if ( ! fbo ) {
return nullptr ;
}
2017-05-16 16:00:34 +02:00
2018-05-06 08:57:44 -07:00
const TempFBOInfo info = { fbo , gpuStats . numFlips } ;
2017-02-07 00:24:38 +01:00
tempFBOs_ [ key ] = info ;
return fbo ;
}
2017-02-07 00:19:31 +01:00
2015-03-14 14:58:32 -07: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 ) ;
2022-08-17 19:55:19 +02:00
checkFlag ( FB_USAGE_RENDER_COLOR , vfb - > last_frame_render ) ;
2015-04-28 00:08:00 -07:00
checkFlag ( FB_USAGE_CLUT , vfb - > last_frame_clut ) ;
2015-03-14 14:58:32 -07:00
}
2015-09-23 12:25:38 +02:00
void FramebufferManagerCommon : : ShowScreenResolution ( ) {
2020-01-26 10:43:18 -08:00
auto gr = GetI18NCategory ( " Graphics " ) ;
2015-09-23 12:25:38 +02:00
std : : ostringstream messageStream ;
messageStream < < gr - > T ( " Internal Resolution " ) < < " : " ;
messageStream < < PSP_CoreParameter ( ) . renderWidth < < " x " < < PSP_CoreParameter ( ) . renderHeight < < " " ;
2015-12-27 12:05:12 -08:00
if ( postShaderIsUpscalingFilter_ ) {
messageStream < < gr - > T ( " (upscaling) " ) < < " " ;
2020-05-16 00:31:14 -07:00
} else if ( postShaderIsSupersampling_ ) {
2018-04-01 17:00:10 +02:00
messageStream < < gr - > T ( " (supersampling) " ) < < " " ;
2015-12-27 12:05:12 -08:00
}
2015-09-23 12:25:38 +02:00
messageStream < < gr - > T ( " Window Size " ) < < " : " ;
messageStream < < PSP_CoreParameter ( ) . pixelWidth < < " x " < < PSP_CoreParameter ( ) . pixelHeight ;
2016-05-27 22:00:14 -07:00
host - > NotifyUserMessage ( messageStream . str ( ) , 2.0f , 0xFFFFFF , " resize " ) ;
2017-03-06 13:50:22 +01:00
INFO_LOG ( SYSTEM , " %s " , messageStream . str ( ) . c_str ( ) ) ;
2016-09-18 20:18:55 -07:00
}
2017-10-11 13:37:00 +02:00
2017-10-22 10:10:59 +02: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
2022-08-29 23:59:43 +02:00
// and read on the next frame, then call the callback.
2017-10-22 10:10:59 +02:00
//
// 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.)
2022-08-16 22:39:09 +02:00
bool FramebufferManagerCommon : : GetFramebuffer ( u32 fb_address , int fb_stride , GEBufferFormat format , GPUDebugBuffer & buffer , int maxScaleFactor ) {
2017-10-11 13:37:00 +02:00
VirtualFramebuffer * vfb = currentRenderVfb_ ;
2022-09-20 14:01:36 -07:00
if ( ! vfb | | vfb - > fb_address ! = fb_address ) {
vfb = ResolveVFB ( fb_address , fb_stride , format ) ;
2017-10-11 13:37:00 +02:00
}
if ( ! vfb ) {
2021-01-31 12:16:30 -08:00
if ( ! Memory : : IsValidAddress ( fb_address ) )
return false ;
2017-10-11 13:37:00 +02:00
// If there's no vfb and we're drawing there, must be memory?
2022-07-20 12:40:22 +02:00
buffer = GPUDebugBuffer ( Memory : : GetPointerWriteUnchecked ( fb_address ) , fb_stride , 512 , format ) ;
2017-10-11 13:37:00 +02:00
return true ;
}
int w = vfb - > renderWidth , h = vfb - > renderHeight ;
Draw : : Framebuffer * bound = nullptr ;
if ( vfb - > fbo ) {
2022-08-16 22:39:09 +02:00
if ( maxScaleFactor > 0 & & vfb - > renderWidth > vfb - > width * maxScaleFactor ) {
w = vfb - > width * maxScaleFactor ;
h = vfb - > height * maxScaleFactor ;
2017-10-11 13:37:00 +02:00
2018-05-06 08:57:44 -07:00
Draw : : Framebuffer * tempFBO = GetTempFBO ( TempFBO : : COPY , w , h ) ;
2017-10-11 13:37:00 +02:00
VirtualFramebuffer tempVfb = * vfb ;
tempVfb . fbo = tempFBO ;
tempVfb . bufferWidth = vfb - > width ;
tempVfb . bufferHeight = vfb - > height ;
tempVfb . renderWidth = w ;
tempVfb . renderHeight = h ;
2022-08-16 22:39:09 +02:00
tempVfb . renderScaleFactor = maxScaleFactor ;
2022-08-29 23:59:43 +02:00
BlitFramebuffer ( & tempVfb , 0 , 0 , vfb , 0 , 0 , vfb - > width , vfb - > height , 0 , RASTER_COLOR , " Blit_GetFramebuffer " ) ;
2017-10-11 13:37:00 +02:00
bound = tempFBO ;
} else {
bound = vfb - > fbo ;
}
}
2017-10-18 11:40:07 +02: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 15:55:24 -08:00
bool flipY = ( GetGPUBackend ( ) = = GPUBackend : : OPENGL & & ! useBufferedRendering_ ) ? true : false ;
2018-06-16 13:30:18 -07:00
buffer . Allocate ( w , h , GE_FORMAT_8888 , flipY ) ;
2023-02-04 23:40:36 +01:00
bool retval = draw_ - > CopyFramebufferToMemory ( bound , Draw : : FB_COLOR_BIT , 0 , 0 , w , h , Draw : : DataFormat : : R8G8B8A8_UNORM , buffer . GetData ( ) , w , Draw : : ReadbackMode : : BLOCK , " GetFramebuffer " ) ;
2023-02-05 16:59:23 +01:00
// Don't need to increment gpu stats for readback count here, this is a debugger-only function.
2017-12-27 13:03:04 +01:00
// After a readback we'll have flushed and started over, need to dirty a bunch of things to be safe.
2020-05-24 20:57:59 +02:00
gstate_c . Dirty ( DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS ) ;
2017-10-11 13:37:00 +02:00
// We may have blitted to a temp FBO.
2020-06-02 09:51:38 +02:00
RebindFramebuffer ( " RebindFramebuffer - GetFramebuffer " ) ;
2017-10-11 13:37:00 +02:00
return retval ;
}
2017-10-11 15:21:53 +02: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 ) {
2021-01-31 12:16:30 -08:00
if ( ! Memory : : IsValidAddress ( z_address ) )
return false ;
2017-10-11 15:21:53 +02:00
// If there's no vfb and we're drawing there, must be memory?
2022-07-20 12:40:22 +02:00
buffer = GPUDebugBuffer ( Memory : : GetPointerWriteUnchecked ( z_address ) , z_stride , 512 , GPU_DBG_FORMAT_16BIT ) ;
2017-10-11 15:21:53 +02:00
return true ;
}
2017-10-18 11:40:07 +02: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 15:21:53 +02:00
}
2017-12-26 15:55:24 -08:00
bool flipY = ( GetGPUBackend ( ) = = GPUBackend : : OPENGL & & ! useBufferedRendering_ ) ? true : false ;
2023-02-03 18:51:59 +01:00
2023-02-05 22:57:33 +01:00
// Old code
if ( gstate_c . Use ( GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT ) ) {
buffer . Allocate ( w , h , GPU_DBG_FORMAT_FLOAT_DIV_256 , flipY ) ;
} else {
buffer . Allocate ( w , h , GPU_DBG_FORMAT_FLOAT , flipY ) ;
}
// No need to free on failure, that's the caller's job (it likely will reuse a buffer.)
bool retval = draw_ - > CopyFramebufferToMemory ( vfb - > fbo , Draw : : FB_DEPTH_BIT , 0 , 0 , w , h , Draw : : DataFormat : : D32F , buffer . GetData ( ) , w , Draw : : ReadbackMode : : BLOCK , " GetDepthBuffer " ) ;
if ( ! retval ) {
// Try ReadbackDepthbufferSync, in case GLES.
buffer . Allocate ( w , h , GPU_DBG_FORMAT_16BIT , flipY ) ;
retval = ReadbackDepthbuffer ( vfb - > fbo , 0 , 0 , w , h , ( uint16_t * ) buffer . GetData ( ) , w , w , h , Draw : : ReadbackMode : : BLOCK ) ;
}
2022-10-10 00:53:31 -07:00
2017-12-27 14:33:18 +01:00
// After a readback we'll have flushed and started over, need to dirty a bunch of things to be safe.
2020-05-24 20:57:59 +02:00
gstate_c . Dirty ( DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS ) ;
2017-12-22 12:29:08 -08:00
// That may have unbound the framebuffer, rebind to avoid crashes when debugging.
2020-06-02 09:51:38 +02:00
RebindFramebuffer ( " RebindFramebuffer - GetDepthbuffer " ) ;
2017-12-22 12:29:08 -08:00
return retval ;
2017-10-11 15:21:53 +02:00
}
bool FramebufferManagerCommon : : GetStencilbuffer ( u32 fb_address , int fb_stride , GPUDebugBuffer & buffer ) {
VirtualFramebuffer * vfb = currentRenderVfb_ ;
if ( ! vfb ) {
vfb = GetVFBAt ( fb_address ) ;
}
if ( ! vfb ) {
2021-01-31 12:16:30 -08:00
if ( ! Memory : : IsValidAddress ( fb_address ) )
return false ;
2017-10-11 15:21:53 +02:00
// If there's no vfb and we're drawing there, must be memory?
// TODO: Actually get the stencil.
2022-07-20 12:40:22 +02:00
buffer = GPUDebugBuffer ( Memory : : GetPointerWrite ( fb_address ) , fb_stride , 512 , GPU_DBG_FORMAT_8888 ) ;
2017-10-11 15:21:53 +02:00
return true ;
}
2017-10-18 11:40:07 +02: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 15:55:24 -08:00
bool flipY = ( GetGPUBackend ( ) = = GPUBackend : : OPENGL & & ! useBufferedRendering_ ) ? true : false ;
2017-12-22 12:29:08 -08:00
// No need to free on failure, the caller/destructor will do that. Usually this is a reused buffer, anyway.
2017-10-18 11:40:07 +02:00
buffer . Allocate ( w , h , GPU_DBG_FORMAT_8BIT , flipY ) ;
2023-02-04 23:40:36 +01:00
bool retval = draw_ - > CopyFramebufferToMemory ( vfb - > fbo , Draw : : FB_STENCIL_BIT , 0 , 0 , w , h , Draw : : DataFormat : : S8 , buffer . GetData ( ) , w , Draw : : ReadbackMode : : BLOCK , " GetStencilbuffer " ) ;
2022-10-10 17:09:14 -07:00
if ( ! retval ) {
2023-02-05 10:52:52 +01:00
retval = ReadbackStencilbuffer ( vfb - > fbo , 0 , 0 , w , h , buffer . GetData ( ) , w , Draw : : ReadbackMode : : BLOCK ) ;
2022-10-10 17:09:14 -07:00
}
2017-12-22 12:29:08 -08:00
// That may have unbound the framebuffer, rebind to avoid crashes when debugging.
2020-06-02 09:51:38 +02:00
RebindFramebuffer ( " RebindFramebuffer - GetStencilbuffer " ) ;
2017-12-22 12:29:08 -08:00
return retval ;
2017-10-11 15:21:53 +02:00
}
2017-10-16 16:27:16 +02:00
bool FramebufferManagerCommon : : GetOutputFramebuffer ( GPUDebugBuffer & buffer ) {
int w , h ;
draw_ - > GetFramebufferDimensions ( nullptr , & w , & h ) ;
2018-06-16 13:47:51 -07: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 ) ;
2023-02-04 23:40:36 +01:00
bool retval = draw_ - > CopyFramebufferToMemory ( nullptr , Draw : : FB_COLOR_BIT , 0 , 0 , w , h , fmt , buffer . GetData ( ) , w , Draw : : ReadbackMode : : BLOCK , " GetOutputFramebuffer " ) ;
2017-12-22 12:29:08 -08:00
// That may have unbound the framebuffer, rebind to avoid crashes when debugging.
2020-06-02 09:51:38 +02:00
RebindFramebuffer ( " RebindFramebuffer - GetOutputFramebuffer " ) ;
2017-12-22 12:29:08 -08:00
return retval ;
2017-10-16 16:27:16 +02:00
}
2023-02-04 11:36:48 +01:00
// This reads a channel of a framebuffer into emulated PSP VRAM, taking care of scaling down as needed.
2022-08-29 23:59:43 +02:00
//
2017-10-22 10:10:59 +02: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
2023-02-05 10:52:52 +01:00
void FramebufferManagerCommon : : ReadbackFramebuffer ( VirtualFramebuffer * vfb , int x , int y , int w , int h , RasterChannel channel , Draw : : ReadbackMode mode ) {
2018-10-28 14:30:39 +01:00
if ( w < = 0 | | h < = 0 ) {
2022-10-09 13:49:41 -07:00
ERROR_LOG ( G3D , " Bad inputs to ReadbackFramebufferSync: %d %d %d %d " , x , y , w , h ) ;
2018-10-28 14:30:39 +01:00
return ;
}
2023-02-04 12:05:50 +01:00
// Note that ReadbackDepthBufferSync can stretch on its own while converting data format, so we don't need to downscale in that case.
if ( vfb - > renderScaleFactor = = 1 | | channel = = RASTER_DEPTH ) {
2023-02-04 11:36:48 +01:00
// No need to stretch-blit
} else {
VirtualFramebuffer * nvfb = FindDownloadTempBuffer ( vfb , channel ) ;
if ( nvfb ) {
BlitFramebuffer ( nvfb , x , y , vfb , x , y , w , h , 0 , channel , " Blit_ReadFramebufferToMemory " ) ;
vfb = nvfb ;
}
}
2022-10-10 00:53:31 -07:00
const u32 fb_address = channel = = RASTER_COLOR ? vfb - > fb_address : vfb - > z_address ;
2017-10-18 11:20:58 +02:00
2022-08-29 23:59:43 +02:00
Draw : : DataFormat destFormat = channel = = RASTER_COLOR ? GEFormatToThin3D ( vfb - > fb_format ) : GEFormatToThin3D ( GE_FORMAT_DEPTH16 ) ;
2017-10-18 11:20:58 +02:00
const int dstBpp = ( int ) DataFormatSizeInBytes ( destFormat ) ;
2022-08-29 23:59:43 +02:00
int stride = channel = = RASTER_COLOR ? vfb - > fb_stride : vfb - > z_stride ;
const int dstByteOffset = ( y * stride + x ) * dstBpp ;
2022-08-30 06:41:37 +02:00
// Leave the gap between the end of the last line and the full stride.
// This is only used for the NotifyMemInfo range.
2022-10-09 00:54:59 -07:00
const int dstSize = ( ( h - 1 ) * stride + w ) * dstBpp ;
2018-10-28 14:30:39 +01:00
2021-01-31 00:22:49 -08:00
if ( ! Memory : : IsValidRange ( fb_address + dstByteOffset , dstSize ) ) {
2022-10-09 13:49:41 -07:00
ERROR_LOG_REPORT ( G3D , " ReadbackFramebufferSync would write outside of memory, ignoring " ) ;
2018-10-28 14:30:39 +01:00
return ;
}
2022-07-20 12:40:22 +02:00
u8 * destPtr = Memory : : GetPointerWriteUnchecked ( fb_address + dstByteOffset ) ;
2017-10-18 11:20:58 +02:00
// We always need to convert from the framebuffer native format.
// Right now that's always 8888.
2018-10-28 14:30:39 +01:00
DEBUG_LOG ( G3D , " Reading framebuffer to mem, fb_address = %08x, ptr=%p " , fb_address , destPtr ) ;
2017-10-18 11:20:58 +02:00
2022-10-10 00:53:31 -07:00
if ( channel = = RASTER_DEPTH ) {
_assert_msg_ ( vfb & & vfb - > z_address ! = 0 & & vfb - > z_stride ! = 0 , " Depth buffer invalid " ) ;
2023-02-05 10:52:52 +01:00
ReadbackDepthbuffer ( vfb - > fbo ,
2023-02-04 12:05:50 +01:00
x * vfb - > renderScaleFactor , y * vfb - > renderScaleFactor ,
2023-02-05 10:52:52 +01:00
w * vfb - > renderScaleFactor , h * vfb - > renderScaleFactor , ( uint16_t * ) destPtr , stride , w , h , mode ) ;
2018-10-28 14:30:39 +01:00
} else {
2023-02-05 10:52:52 +01:00
draw_ - > CopyFramebufferToMemory ( vfb - > fbo , channel = = RASTER_COLOR ? Draw : : FB_COLOR_BIT : Draw : : FB_DEPTH_BIT , x , y , w , h , destFormat , destPtr , stride , mode , " ReadbackFramebufferSync " ) ;
2018-10-28 14:30:39 +01:00
}
2018-03-16 15:52:43 +01:00
2022-10-10 00:53:31 -07:00
char tag [ 128 ] ;
size_t len = snprintf ( tag , sizeof ( tag ) , " FramebufferPack/%08x_%08x_%dx%d_%s " , vfb - > fb_address , vfb - > z_address , w , h , GeBufferFormatToString ( vfb - > fb_format ) ) ;
NotifyMemInfo ( MemBlockFlags : : WRITE , fb_address + dstByteOffset , dstSize , tag , len ) ;
2023-02-05 16:59:23 +01:00
if ( mode = = Draw : : ReadbackMode : : BLOCK ) {
gpuStats . numBlockingReadbacks + + ;
} else {
gpuStats . numReadbacks + + ;
}
2017-10-18 11:20:58 +02:00
}
2023-02-05 10:52:52 +01:00
bool FramebufferManagerCommon : : ReadbackStencilbuffer ( Draw : : Framebuffer * fbo , int x , int y , int w , int h , uint8_t * pixels , int pixelsStride , Draw : : ReadbackMode mode ) {
return draw_ - > CopyFramebufferToMemory ( fbo , Draw : : FB_DEPTH_BIT , x , y , w , h , Draw : : DataFormat : : S8 , pixels , pixelsStride , mode , " ReadbackStencilbufferSync " ) ;
2022-10-10 17:09:14 -07:00
}
2023-02-05 10:52:52 +01:00
void FramebufferManagerCommon : : ReadFramebufferToMemory ( VirtualFramebuffer * vfb , int x , int y , int w , int h , RasterChannel channel , Draw : : ReadbackMode mode ) {
2017-11-05 10:01:03 +01:00
// Clamp to bufferWidth. Sometimes block transfers can cause this to hit.
if ( x + w > = vfb - > bufferWidth ) {
w = vfb - > bufferWidth - x ;
2017-11-01 14:43:00 +01:00
}
2017-11-30 15:37:43 -08:00
if ( vfb & & vfb - > fbo ) {
2020-11-05 08:49:55 +01:00
if ( gameUsesSequentialCopies_ ) {
2022-10-09 13:21:04 -07:00
// Ignore the x/y/etc., read the entire thing. See below.
2020-11-05 08:49:55 +01:00
x = 0 ;
y = 0 ;
w = vfb - > width ;
h = vfb - > height ;
vfb - > memoryUpdated = true ;
vfb - > usageFlags | = FB_USAGE_DOWNLOAD ;
} else if ( x = = 0 & & y = = 0 & & w = = vfb - > width & & h = = vfb - > height ) {
// Mark it as fully downloaded until next render to it.
2022-10-09 00:50:45 -07:00
if ( channel = = RASTER_COLOR )
vfb - > memoryUpdated = true ;
2020-11-05 08:49:55 +01:00
vfb - > usageFlags | = FB_USAGE_DOWNLOAD ;
} else {
// Let's try to set the flag eventually, if the game copies a lot.
2022-10-09 13:21:04 -07:00
// Some games (like Grand Knights History) copy subranges very frequently.
2020-11-05 08:49:55 +01:00
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 ;
}
}
2023-02-04 11:36:48 +01:00
// This handles any required stretching internally.
2023-02-05 10:52:52 +01:00
ReadbackFramebuffer ( vfb , x , y , w , h , channel , mode ) ;
2017-10-18 11:20:58 +02:00
2022-12-01 19:15:38 +01:00
draw_ - > Invalidate ( InvalidationFlags : : CACHED_RENDER_STATE ) ;
2017-10-18 11:20:58 +02:00
textureCache_ - > ForgetLastTexture ( ) ;
2020-06-02 09:51:38 +02:00
RebindFramebuffer ( " RebindFramebuffer - ReadFramebufferToMemory " ) ;
2017-10-18 11:20:58 +02:00
}
}
2017-10-18 12:26:02 +02: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.
2022-08-29 10:14:29 +02:00
// Only bother if any draws are pending.
if ( drawEngine_ - > GetNumDrawCalls ( ) > 0 ) {
// 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 12:26:02 +02:00
}
2017-10-18 12:34:01 +02:00
2022-08-22 23:30:28 +02:00
// TODO: Replace with with depal, reading the palette from the texture on the GPU directly.
2017-10-18 12:34:01 +02:00
void FramebufferManagerCommon : : DownloadFramebufferForClut ( u32 fb_address , u32 loadBytes ) {
VirtualFramebuffer * vfb = GetVFBAt ( fb_address ) ;
if ( vfb & & vfb - > fb_stride ! = 0 ) {
2022-08-22 23:30:28 +02:00
const u32 bpp = BufferFormatBytesPerPixel ( vfb - > fb_format ) ;
2017-10-18 12:34:01 +02:00
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 ) ;
2022-09-11 10:30:43 +02:00
if ( w = = 0 | | h > 1 ) {
// Exactly aligned, or more than one row.
w = std : : min ( vfb - > fb_stride , vfb - > width ) ;
}
2017-10-18 12:34:01 +02:00
// 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 23:49:09 +01:00
if ( w > 0 & & h > 0 & & ! vfb - > memoryUpdated & & vfb - > clutUpdatedBytes < loadBytes ) {
2020-11-05 08:49:55 +01:00
// We intentionally don't try to optimize into a full download here - we don't want to over download.
2017-10-18 12:34:01 +02:00
// 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 ;
2023-02-04 11:36:48 +01:00
// This function now handles scaling down internally.
2023-02-05 10:52:52 +01:00
ReadbackFramebuffer ( vfb , x , y , w , h , RASTER_COLOR , Draw : : ReadbackMode : : BLOCK ) ;
2017-10-18 12:34:01 +02:00
textureCache_ - > ForgetLastTexture ( ) ;
2020-06-02 09:51:38 +02:00
RebindFramebuffer ( " RebindFramebuffer - DownloadFramebufferForClut " ) ;
2017-10-18 12:34:01 +02:00
}
}
}
2017-10-18 12:49:15 +02:00
2020-06-02 09:51:38 +02:00
void FramebufferManagerCommon : : RebindFramebuffer ( const char * tag ) {
2022-12-01 19:15:38 +01:00
draw_ - > Invalidate ( InvalidationFlags : : CACHED_RENDER_STATE ) ;
2018-07-28 11:09:01 +02:00
shaderManager_ - > DirtyLastShader ( ) ;
2022-12-10 12:28:16 +01:00
// Needed for D3D11 to run validation clean. I don't think it's actually an issue.
// textureCache_->ForgetLastTexture();
2017-11-15 20:44:25 +01:00
if ( currentRenderVfb_ & & currentRenderVfb_ - > fbo ) {
2022-10-23 11:21:35 +02:00
draw_ - > BindFramebufferAsRenderTarget ( currentRenderVfb_ - > fbo , { Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP } , tag ) ;
2017-11-15 20:44:25 +01:00
} else {
2017-12-22 12:29:08 -08:00
// Should this even happen? It could while debugging, but maybe we can just skip binding at all.
2022-10-23 11:21:35 +02:00
draw_ - > BindFramebufferAsRenderTarget ( nullptr , { Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP } , " RebindFramebuffer_Bad " ) ;
2017-11-15 20:44:25 +01:00
}
}
2022-08-16 10:55:44 +02:00
std : : vector < FramebufferInfo > FramebufferManagerCommon : : GetFramebufferList ( ) const {
2017-10-18 12:49:15 +02:00
std : : vector < FramebufferInfo > list ;
2022-08-25 00:52:45 +02:00
for ( auto vfb : vfbs_ ) {
2017-10-18 12:49:15 +02:00
FramebufferInfo info ;
info . fb_address = vfb - > fb_address ;
info . z_address = vfb - > z_address ;
2022-08-22 21:28:43 +02:00
info . format = vfb - > fb_format ;
2017-10-18 12:49:15 +02:00
info . width = vfb - > width ;
info . height = vfb - > height ;
info . fbo = vfb - > fbo ;
list . push_back ( info ) ;
}
return list ;
}
2020-11-07 00:56:48 +01:00
2021-08-07 22:22:11 -07:00
template < typename T >
static void DoRelease ( T * & obj ) {
if ( obj )
obj - > Release ( ) ;
obj = nullptr ;
}
2022-12-01 23:41:31 +01:00
void FramebufferManagerCommon : : ReleasePipelines ( ) {
2022-08-26 12:16:56 +02:00
for ( int i = 0 ; i < ARRAY_SIZE ( reinterpretFromTo_ ) ; i + + ) {
for ( int j = 0 ; j < ARRAY_SIZE ( reinterpretFromTo_ ) ; j + + ) {
2021-08-07 22:22:11 -07:00
DoRelease ( reinterpretFromTo_ [ i ] [ j ] ) ;
2020-11-07 11:20:22 +01:00
}
}
2022-10-10 16:54:29 -07:00
DoRelease ( stencilWriteSampler_ ) ;
DoRelease ( stencilWritePipeline_ ) ;
2022-10-10 17:09:14 -07:00
DoRelease ( stencilReadbackSampler_ ) ;
DoRelease ( stencilReadbackPipeline_ ) ;
2022-10-10 00:40:54 -07:00
DoRelease ( depthReadbackSampler_ ) ;
DoRelease ( depthReadbackPipeline_ ) ;
2022-08-16 12:46:13 +02:00
DoRelease ( draw2DPipelineColor_ ) ;
2022-09-21 18:33:15 +02:00
DoRelease ( draw2DPipelineColorRect2Lin_ ) ;
2022-08-16 12:46:13 +02:00
DoRelease ( draw2DPipelineDepth_ ) ;
2022-08-17 14:28:34 +02:00
DoRelease ( draw2DPipeline565ToDepth_ ) ;
2022-08-18 15:46:20 +02:00
DoRelease ( draw2DPipeline565ToDepthDeswizzle_ ) ;
2022-12-01 23:41:31 +01:00
}
2022-08-16 12:46:13 +02:00
2022-12-01 23:41:31 +01:00
void FramebufferManagerCommon : : DeviceLost ( ) {
DestroyAllFBOs ( ) ;
presentation_ - > DeviceLost ( ) ;
2022-08-23 10:35:58 +02:00
draw2D_ . DeviceLost ( ) ;
2022-12-01 23:41:31 +01:00
ReleasePipelines ( ) ;
2020-11-07 00:56:48 +01:00
draw_ = nullptr ;
}
void FramebufferManagerCommon : : DeviceRestore ( Draw : : DrawContext * draw ) {
draw_ = draw ;
2022-08-23 10:35:58 +02:00
draw2D_ . DeviceRestore ( draw_ ) ;
2022-08-23 20:15:30 -07:00
presentation_ - > DeviceRestore ( draw_ ) ;
2020-11-07 00:56:48 +01:00
}
2022-08-03 12:14:28 +02:00
void FramebufferManagerCommon : : DrawActiveTexture ( float x , float y , float w , float h , float destW , float destH , float u0 , float v0 , float u1 , float v1 , int uvRotation , int flags ) {
// Will be drawn as a strip.
Draw2DVertex coord [ 4 ] = {
{ x , y , u0 , v0 } ,
{ x + w , y , u1 , v0 } ,
{ x + w , y + h , u1 , v1 } ,
{ x , y + h , u0 , v1 } ,
} ;
if ( uvRotation ! = ROTATION_LOCKED_HORIZONTAL ) {
float temp [ 8 ] ;
int rotation = 0 ;
switch ( uvRotation ) {
case ROTATION_LOCKED_HORIZONTAL180 : rotation = 2 ; break ;
case ROTATION_LOCKED_VERTICAL : rotation = 1 ; break ;
case ROTATION_LOCKED_VERTICAL180 : rotation = 3 ; break ;
}
for ( int i = 0 ; i < 4 ; i + + ) {
temp [ i * 2 ] = coord [ ( ( i + rotation ) & 3 ) ] . u ;
temp [ i * 2 + 1 ] = coord [ ( ( i + rotation ) & 3 ) ] . v ;
}
for ( int i = 0 ; i < 4 ; i + + ) {
coord [ i ] . u = temp [ i * 2 ] ;
coord [ i ] . v = temp [ i * 2 + 1 ] ;
}
}
const float invDestW = 2.0f / destW ;
const float invDestH = 2.0f / destH ;
for ( int i = 0 ; i < 4 ; i + + ) {
coord [ i ] . x = coord [ i ] . x * invDestW - 1.0f ;
coord [ i ] . y = coord [ i ] . y * invDestH - 1.0f ;
}
if ( ( flags & DRAWTEX_TO_BACKBUFFER ) & & g_display_rotation ! = DisplayRotation : : ROTATE_0 ) {
for ( int i = 0 ; i < 4 ; i + + ) {
// backwards notation, should fix that...
Lin : : Vec3 pos = Lin : : Vec3 ( coord [ i ] . x , coord [ i ] . y , 0.0 ) ;
pos = pos * g_display_rot_matrix ;
coord [ i ] . x = pos . x ;
coord [ i ] . y = pos . y ;
}
}
// Rearrange to strip form.
std : : swap ( coord [ 2 ] , coord [ 3 ] ) ;
2022-09-12 15:34:32 +02:00
draw2D_ . DrawStrip2D ( nullptr , coord , 4 , ( flags & DRAWTEX_LINEAR ) ! = 0 , Get2DPipeline ( ( flags & DRAWTEX_DEPTH ) ? DRAW2D_COPY_DEPTH : DRAW2D_COPY_COLOR ) ) ;
2022-08-03 12:14:28 +02:00
2022-09-22 09:12:20 +02:00
gstate_c . Dirty ( DIRTY_ALL_RENDER_STATE ) ;
2022-08-03 12:14:28 +02:00
}
2022-08-03 15:41:17 +02:00
2022-08-29 23:59:43 +02:00
void FramebufferManagerCommon : : BlitFramebuffer ( VirtualFramebuffer * dst , int dstX , int dstY , VirtualFramebuffer * src , int srcX , int srcY , int w , int h , int bpp , RasterChannel channel , const char * tag ) {
2022-08-03 15:41:17 +02:00
if ( ! dst - > fbo | | ! src - > fbo | | ! useBufferedRendering_ ) {
// This can happen if they recently switched from non-buffered.
if ( useBufferedRendering_ ) {
2022-08-07 13:19:27 +02:00
// Just bind the back buffer for rendering, forget about doing anything else as we're in a weird state.
2022-10-23 11:21:35 +02:00
draw_ - > BindFramebufferAsRenderTarget ( nullptr , { Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP } , " BlitFramebuffer " ) ;
2022-08-03 15:41:17 +02:00
}
return ;
}
2022-09-22 09:57:53 +02:00
if ( channel = = RASTER_DEPTH & & ! draw_ - > GetDeviceCaps ( ) . fragmentShaderDepthWriteSupported ) {
// Can't do anything :(
return ;
}
2022-08-03 16:00:19 +02:00
// Perform a little bit of clipping first.
// Block transfer coords are unsigned so I don't think we need to clip on the left side.. Although there are
// other uses for BlitFramebuffer.
if ( dstX + w > dst - > bufferWidth ) {
w - = dstX + w - dst - > bufferWidth ;
}
if ( dstY + h > dst - > bufferHeight ) {
h - = dstY + h - dst - > bufferHeight ;
}
if ( srcX + w > src - > bufferWidth ) {
w - = srcX + w - src - > bufferWidth ;
}
if ( srcY + h > src - > bufferHeight ) {
h - = srcY + h - src - > bufferHeight ;
}
if ( w < = 0 | | h < = 0 ) {
// The whole rectangle got clipped.
return ;
}
2022-08-29 23:59:43 +02:00
bool useBlit = channel = = RASTER_COLOR ? draw_ - > GetDeviceCaps ( ) . framebufferBlitSupported : false ;
bool useCopy = channel = = RASTER_COLOR ? draw_ - > GetDeviceCaps ( ) . framebufferCopySupported : false ;
2022-11-28 23:30:35 +01:00
if ( dst = = currentRenderVfb_ | | dst - > fbo - > MultiSampleLevel ( ) ! = 0 | | src - > fbo - > MultiSampleLevel ( ) ! = 0 ) {
2022-08-03 15:41:17 +02:00
// If already bound, using either a blit or a copy is unlikely to be an optimization.
2022-11-28 23:30:35 +01:00
// So we're gonna use a raster draw instead. Also multisampling has problems with copies currently.
2022-08-03 15:41:17 +02:00
useBlit = false ;
useCopy = false ;
}
2022-08-03 17:26:07 +02:00
float srcXFactor = src - > renderScaleFactor ;
float srcYFactor = src - > renderScaleFactor ;
2022-08-22 21:28:43 +02:00
const int srcBpp = BufferFormatBytesPerPixel ( src - > fb_format ) ;
2022-08-03 15:41:17 +02:00
if ( srcBpp ! = bpp & & bpp ! = 0 ) {
2022-08-17 14:42:13 +02:00
// If we do this, we're kinda in nonsense territory since the actual formats won't match (unless intentionally blitting black or white).
2022-08-03 15:41:17 +02:00
srcXFactor = ( srcXFactor * bpp ) / srcBpp ;
}
int srcX1 = srcX * srcXFactor ;
int srcX2 = ( srcX + w ) * srcXFactor ;
int srcY1 = srcY * srcYFactor ;
int srcY2 = ( srcY + h ) * srcYFactor ;
2022-08-03 17:26:07 +02:00
float dstXFactor = dst - > renderScaleFactor ;
float dstYFactor = dst - > renderScaleFactor ;
2022-08-22 21:28:43 +02:00
const int dstBpp = BufferFormatBytesPerPixel ( dst - > fb_format ) ;
2022-08-03 15:41:17 +02:00
if ( dstBpp ! = bpp & & bpp ! = 0 ) {
2022-08-17 14:42:13 +02:00
// If we do this, we're kinda in nonsense territory since the actual formats won't match (unless intentionally blitting black or white).
2022-08-03 15:41:17 +02:00
dstXFactor = ( dstXFactor * bpp ) / dstBpp ;
}
int dstX1 = dstX * dstXFactor ;
int dstX2 = ( dstX + w ) * dstXFactor ;
int dstY1 = dstY * dstYFactor ;
int dstY2 = ( dstY + h ) * dstYFactor ;
if ( src = = dst & & srcX = = dstX & & srcY = = dstY ) {
// Let's just skip a copy where the destination is equal to the source.
WARN_LOG_REPORT_ONCE ( blitSame , G3D , " Skipped blit with equal dst and src " ) ;
return ;
}
if ( useCopy ) {
// glBlitFramebuffer can clip, but glCopyImageSubData is more restricted.
// In case the src goes outside, we just skip the optimization in that case.
const bool sameSize = dstX2 - dstX1 = = srcX2 - srcX1 & & dstY2 - dstY1 = = srcY2 - srcY1 ;
const bool srcInsideBounds = srcX2 < = src - > renderWidth & & srcY2 < = src - > renderHeight ;
const bool dstInsideBounds = dstX2 < = dst - > renderWidth & & dstY2 < = dst - > renderHeight ;
const bool xOverlap = src = = dst & & srcX2 > dstX1 & & srcX1 < dstX2 ;
const bool yOverlap = src = = dst & & srcY2 > dstY1 & & srcY1 < dstY2 ;
if ( sameSize & & srcInsideBounds & & dstInsideBounds & & ! ( xOverlap & & yOverlap ) ) {
2022-08-07 13:19:27 +02:00
draw_ - > CopyFramebufferImage ( src - > fbo , 0 , srcX1 , srcY1 , 0 , dst - > fbo , 0 , dstX1 , dstY1 , 0 , dstX2 - dstX1 , dstY2 - dstY1 , 1 ,
channel = = RASTER_COLOR ? Draw : : FB_COLOR_BIT : Draw : : FB_DEPTH_BIT , tag ) ;
2022-08-03 15:41:17 +02:00
return ;
}
}
if ( useBlit ) {
2022-08-07 13:19:27 +02:00
draw_ - > BlitFramebuffer ( src - > fbo , srcX1 , srcY1 , srcX2 , srcY2 , dst - > fbo , dstX1 , dstY1 , dstX2 , dstY2 ,
channel = = RASTER_COLOR ? Draw : : FB_COLOR_BIT : Draw : : FB_DEPTH_BIT , Draw : : FB_BLIT_NEAREST , tag ) ;
2022-08-03 15:41:17 +02:00
} else {
2022-08-23 10:05:44 +02:00
Draw2DPipeline * pipeline = Get2DPipeline ( channel = = RASTER_COLOR ? DRAW2D_COPY_COLOR : DRAW2D_COPY_DEPTH ) ;
2022-08-03 15:49:17 +02:00
Draw : : Framebuffer * srcFBO = src - > fbo ;
if ( src = = dst ) {
Draw : : Framebuffer * tempFBO = GetTempFBO ( TempFBO : : BLIT , src - > renderWidth , src - > renderHeight ) ;
2022-09-04 23:28:55 +02:00
BlitUsingRaster ( src - > fbo , srcX1 , srcY1 , srcX2 , srcY2 , tempFBO , dstX1 , dstY1 , dstX2 , dstY2 , false , dst - > renderScaleFactor , pipeline , tag ) ;
2022-08-03 15:49:17 +02:00
srcFBO = tempFBO ;
}
2022-09-04 23:28:55 +02:00
BlitUsingRaster ( srcFBO , srcX1 , srcY1 , srcX2 , srcY2 , dst - > fbo , dstX1 , dstY1 , dstX2 , dstY2 , false , dst - > renderScaleFactor , pipeline , tag ) ;
2022-08-03 15:41:17 +02:00
}
2022-12-01 19:15:38 +01:00
draw_ - > Invalidate ( InvalidationFlags : : CACHED_RENDER_STATE ) ;
2022-08-07 13:19:27 +02:00
2022-09-22 09:12:20 +02:00
gstate_c . Dirty ( DIRTY_ALL_RENDER_STATE ) ;
2022-08-03 15:41:17 +02:00
}
2022-08-18 10:51:50 +02:00
// The input is raw pixel coordinates, scale not taken into account.
2022-08-03 15:41:17 +02:00
void FramebufferManagerCommon : : BlitUsingRaster (
Draw : : Framebuffer * src , float srcX1 , float srcY1 , float srcX2 , float srcY2 ,
Draw : : Framebuffer * dest , float destX1 , float destY1 , float destX2 , float destY2 ,
2022-08-07 13:19:27 +02:00
bool linearFilter ,
2022-09-04 23:28:55 +02:00
int scaleFactor ,
2022-08-23 10:05:44 +02:00
Draw2DPipeline * pipeline , const char * tag ) {
2022-08-03 15:41:17 +02:00
2022-08-23 10:05:44 +02:00
if ( pipeline - > info . writeChannel = = RASTER_DEPTH ) {
2022-08-08 12:07:50 +02:00
_dbg_assert_ ( draw_ - > GetDeviceCaps ( ) . fragmentShaderDepthWriteSupported ) ;
}
2022-08-03 15:41:17 +02:00
int destW , destH , srcW , srcH ;
draw_ - > GetFramebufferDimensions ( src , & srcW , & srcH ) ;
draw_ - > GetFramebufferDimensions ( dest , & destW , & destH ) ;
2022-08-07 13:19:27 +02:00
// Unbind the texture first to avoid the D3D11 hazard check (can't set render target to things bound as textures and vice versa, not even temporarily).
draw_ - > BindTexture ( 0 , nullptr ) ;
// This will get optimized away in case it's already bound (in VK and GL at least..)
2022-10-23 11:21:35 +02:00
draw_ - > BindFramebufferAsRenderTarget ( dest , { Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP } , tag ? tag : " BlitUsingRaster " ) ;
2022-10-25 23:59:42 +02:00
draw_ - > BindFramebufferAsTexture ( src , 0 , pipeline - > info . readChannel = = RASTER_COLOR ? Draw : : FB_COLOR_BIT : Draw : : FB_DEPTH_BIT , Draw : : ALL_LAYERS ) ;
2022-08-03 15:41:17 +02:00
2022-08-28 23:16:48 +02:00
if ( destX1 = = 0.0f & & destY1 = = 0.0f & & destX2 > = destW & & destY2 > = destH ) {
// We overwrite the whole channel of the framebuffer, so we can invalidate the current contents.
draw_ - > InvalidateFramebuffer ( Draw : : FB_INVALIDATION_LOAD , pipeline - > info . writeChannel = = RASTER_COLOR ? Draw : : FB_COLOR_BIT : Draw : : FB_DEPTH_BIT ) ;
}
2022-08-07 12:02:06 +02:00
Draw : : Viewport vp { 0.0f , 0.0f , ( float ) dest - > Width ( ) , ( float ) dest - > Height ( ) , 0.0f , 1.0f } ;
draw_ - > SetViewports ( 1 , & vp ) ;
draw_ - > SetScissorRect ( 0 , 0 , ( int ) dest - > Width ( ) , ( int ) dest - > Height ( ) ) ;
2022-08-23 10:35:58 +02:00
2022-09-04 23:28:55 +02:00
draw2D_ . Blit ( pipeline , srcX1 , srcY1 , srcX2 , srcY2 , destX1 , destY1 , destX2 , destY2 , ( float ) srcW , ( float ) srcH , ( float ) destW , ( float ) destH , linearFilter , scaleFactor ) ;
2022-08-03 15:41:17 +02:00
2022-09-22 09:12:20 +02:00
gstate_c . Dirty ( DIRTY_ALL_RENDER_STATE ) ;
2022-08-03 15:41:17 +02:00
}
2022-08-22 23:30:28 +02:00
2022-10-18 00:26:10 +02:00
int FramebufferManagerCommon : : GetFramebufferLayers ( ) const {
int layers = 1 ;
if ( gstate_c . Use ( GPU_USE_SINGLE_PASS_STEREO ) ) {
layers = 2 ;
}
return layers ;
}
2022-08-22 23:30:28 +02:00
VirtualFramebuffer * FramebufferManagerCommon : : ResolveFramebufferColorToFormat ( VirtualFramebuffer * src , GEBufferFormat newFormat ) {
// Look for an identical framebuffer with the new format
_dbg_assert_ ( src - > fb_format ! = newFormat ) ;
VirtualFramebuffer * vfb = nullptr ;
for ( auto dest : vfbs_ ) {
if ( dest = = src ) {
continue ;
}
2022-08-29 10:14:29 +02:00
// Sanity check for things that shouldn't exist.
if ( dest - > fb_address = = src - > fb_address & & dest - > fb_format = = src - > fb_format & & dest - > fb_stride = = src - > fb_stride ) {
_dbg_assert_msg_ ( false , " illegal clone of src found " ) ;
}
2022-08-27 13:57:54 +02:00
if ( dest - > fb_address = = src - > fb_address & & dest - > FbStrideInBytes ( ) = = src - > FbStrideInBytes ( ) & & dest - > fb_format = = newFormat ) {
2022-08-22 23:30:28 +02:00
vfb = dest ;
break ;
}
}
if ( ! vfb ) {
2022-08-23 13:09:29 +02:00
// Create a clone!
vfb = new VirtualFramebuffer ( ) ;
* vfb = * src ; // Copies everything, but watch out! Can't copy fbo.
2022-08-27 11:12:35 +02:00
// Adjust width by bpp.
float widthFactor = ( float ) BufferFormatBytesPerPixel ( vfb - > fb_format ) / ( float ) BufferFormatBytesPerPixel ( newFormat ) ;
vfb - > width * = widthFactor ;
vfb - > bufferWidth * = widthFactor ;
vfb - > renderWidth * = widthFactor ;
vfb - > drawnWidth * = widthFactor ;
vfb - > newWidth * = widthFactor ;
vfb - > safeWidth * = widthFactor ;
2022-08-23 13:09:29 +02:00
vfb - > fb_format = newFormat ;
2022-08-29 10:14:29 +02:00
// stride stays the same since it's in pixels.
2022-08-27 11:31:17 +02:00
WARN_LOG ( G3D , " Creating %s clone of %08x/%08x/%s (%dx%d -> %dx%d) " , GeBufferFormatToString ( newFormat ) , src - > fb_address , src - > z_address , GeBufferFormatToString ( src - > fb_format ) , src - > width , src - > height , vfb - > width , vfb - > height ) ;
2022-08-23 13:09:29 +02:00
char tag [ 128 ] ;
FormatFramebufferName ( vfb , tag , sizeof ( tag ) ) ;
2022-11-28 18:20:30 +01:00
vfb - > fbo = draw_ - > CreateFramebuffer ( { vfb - > renderWidth , vfb - > renderHeight , 1 , GetFramebufferLayers ( ) , 0 , true , tag } ) ;
2022-08-23 13:09:29 +02:00
vfbs_ . push_back ( vfb ) ;
2022-08-22 23:30:28 +02:00
}
// OK, now resolve it so we can texture from it.
// This will do any necessary reinterprets.
CopyToColorFromOverlappingFramebuffers ( vfb ) ;
// Now we consider the resolved one the latest at the address (though really, we could make them equivalent?).
vfb - > colorBindSeq = GetBindSeqCount ( ) ;
return vfb ;
}
2022-08-31 09:12:52 +02:00
static void ApplyKillzoneFramebufferSplit ( FramebufferHeuristicParams * params , int * drawing_width ) {
// Detect whether we're rendering to the margin.
bool margin ;
2022-08-31 11:40:10 +02:00
if ( ( params - > scissorRight - params - > scissorLeft ) = = 32 ) {
// Title screen has this easy case. It also uses non-through verts, so lucky for us that we have this.
2022-08-31 09:12:52 +02:00
margin = true ;
2022-08-31 11:40:10 +02:00
} else if ( params - > scissorRight = = 480 ) {
2022-08-31 09:12:52 +02:00
margin = false ;
} else {
// Go deep, look at the vertices. Killzone-specific, of course.
margin = false ;
if ( ( gstate . vertType & 0xFFFFFF ) = = 0x00800102 ) { // through, u16, s16
u16 * vdata = ( u16 * ) Memory : : GetPointerUnchecked ( gstate_c . vertexAddr ) ;
int v0PosU = vdata [ 0 ] ;
int v0PosX = vdata [ 2 ] ;
if ( v0PosX > = 480 & & v0PosU < 480 ) {
// Texturing from surface, writing to margin
margin = true ;
}
}
2022-09-12 21:26:13 +02:00
// TODO: Implement this for Burnout Dominator. It has to handle self-reads inside
// the margin framebuffer though, so framebuffer copies are still needed, just smaller.
// It uses 0x0080019f (through, float texcoords, ABGR 8888 colors, float positions).
2022-08-31 09:12:52 +02:00
}
if ( margin ) {
gstate_c . SetCurRTOffset ( - 480 , 0 ) ;
// Modify the fb_address and z_address too to avoid matching below.
params - > fb_address + = 480 * 4 ;
params - > z_address + = 480 * 2 ;
* drawing_width = 32 ;
} else {
gstate_c . SetCurRTOffset ( 0 , 0 ) ;
* drawing_width = 480 ;
}
}