2016-01-03 23:09:37 +01:00
// Copyright (c) 2015- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// 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/.
2016-01-10 14:24:10 +01:00
# include <set>
# include <algorithm>
# include "profiler/profiler.h"
# include "base/timeutil.h"
# include "math/lin/matrix4x4.h"
2017-02-12 18:29:58 +01:00
# include "math/dataconv.h"
2017-11-01 23:35:06 +01:00
# include "i18n/i18n.h"
# include "ext/native/file/vfs.h"
2017-02-04 18:46:12 +01:00
# include "ext/native/thin3d/thin3d.h"
2016-01-10 14:24:10 +01:00
2016-02-21 20:21:24 +01:00
# include "Common/Vulkan/VulkanContext.h"
2016-03-28 22:14:04 +02:00
# include "Common/Vulkan/VulkanMemory.h"
# include "Common/Vulkan/VulkanImage.h"
2017-08-22 16:28:35 +02:00
# include "thin3d/VulkanRenderManager.h"
2016-01-10 14:24:10 +01:00
# include "Common/ColorConv.h"
# include "Core/Host.h"
# include "Core/MemMap.h"
# include "Core/Config.h"
# include "Core/System.h"
# include "Core/Reporting.h"
# include "Core/HLE/sceDisplay.h"
# include "GPU/ge_constants.h"
# include "GPU/GPUState.h"
2017-11-01 23:35:06 +01:00
# include "GPU/Common/ShaderTranslation.h"
2016-01-10 14:24:10 +01:00
# include "GPU/Common/PostShader.h"
# include "GPU/Common/TextureDecoder.h"
# include "GPU/Common/FramebufferCommon.h"
# include "GPU/Debugger/Stepping.h"
2016-01-05 21:18:43 +01:00
# include "GPU/GPUInterface.h"
2016-01-05 22:55:47 +01:00
# include "GPU/GPUState.h"
2016-03-17 11:56:43 +01:00
# include "Common/Vulkan/VulkanImage.h"
2016-01-03 23:09:37 +01:00
# include "GPU/Vulkan/FramebufferVulkan.h"
2016-01-05 22:55:47 +01:00
# include "GPU/Vulkan/DrawEngineVulkan.h"
2016-01-10 14:24:10 +01:00
# include "GPU/Vulkan/TextureCacheVulkan.h"
# include "GPU/Vulkan/ShaderManagerVulkan.h"
2016-03-17 11:56:43 +01:00
# include "GPU/Vulkan/VulkanUtil.h"
2016-01-10 14:24:10 +01:00
2016-03-28 22:14:04 +02:00
static const char tex_fs [ ] = R " (#version 400
# extension GL_ARB_separate_shader_objects : enable
# extension GL_ARB_shading_language_420pack : enable
layout ( binding = 0 ) uniform sampler2D sampler0 ;
layout ( location = 0 ) in vec2 v_texcoord0 ;
layout ( location = 0 ) out vec4 fragColor ;
void main ( ) {
fragColor = texture ( sampler0 , v_texcoord0 ) ;
}
) " ;
2016-01-05 21:18:43 +01:00
2016-03-28 22:14:04 +02:00
static const char tex_vs [ ] = R " (#version 400
# extension GL_ARB_separate_shader_objects : enable
# extension GL_ARB_shading_language_420pack : enable
2016-03-30 21:17:20 +02:00
layout ( location = 0 ) in vec3 a_position ;
2016-03-28 22:14:04 +02:00
layout ( location = 1 ) in vec2 a_texcoord0 ;
layout ( location = 0 ) out vec2 v_texcoord0 ;
2016-05-14 16:29:40 +02:00
out gl_PerVertex { vec4 gl_Position ; } ;
2016-03-28 22:14:04 +02:00
void main ( ) {
v_texcoord0 = a_texcoord0 ;
2016-03-30 21:17:20 +02:00
gl_Position = vec4 ( a_position , 1.0 ) ;
2016-03-28 22:14:04 +02:00
}
) " ;
2016-01-10 14:24:10 +01:00
void ConvertFromRGBA8888_Vulkan ( u8 * dst , const u8 * src , u32 dstStride , u32 srcStride , u32 width , u32 height , GEBufferFormat format ) ;
2017-02-05 19:51:50 +01:00
FramebufferManagerVulkan : : FramebufferManagerVulkan ( Draw : : DrawContext * draw , VulkanContext * vulkan ) :
FramebufferManagerCommon ( draw ) ,
2016-03-22 00:12:41 +01:00
vulkan_ ( vulkan ) ,
2016-03-28 22:14:04 +02:00
drawPixelsTex_ ( nullptr ) ,
2016-03-22 00:12:41 +01:00
drawPixelsTexFormat_ ( GE_FORMAT_INVALID ) ,
2016-03-30 22:17:40 +02:00
convBuf_ ( nullptr ) ,
convBufSize_ ( 0 ) ,
2017-02-06 12:02:30 +01:00
textureCacheVulkan_ ( nullptr ) ,
2017-02-15 18:32:44 +01:00
shaderManagerVulkan_ ( nullptr ) ,
2017-11-09 16:58:59 +01:00
pipelinePostShader_ ( VK_NULL_HANDLE ) {
2016-03-22 00:12:41 +01:00
2016-10-09 11:09:30 -07:00
InitDeviceObjects ( ) ;
2017-10-25 20:28:12 +02:00
// After a blit we do need to rebind for the VulkanRenderManager to know what to do.
needGLESRebinds_ = true ;
2016-10-09 11:09:30 -07:00
}
FramebufferManagerVulkan : : ~ FramebufferManagerVulkan ( ) {
delete [ ] convBuf_ ;
DestroyDeviceObjects ( ) ;
}
2017-02-06 12:02:30 +01:00
void FramebufferManagerVulkan : : SetTextureCache ( TextureCacheVulkan * tc ) {
textureCacheVulkan_ = tc ;
textureCache_ = tc ;
}
2017-02-15 18:32:44 +01:00
void FramebufferManagerVulkan : : SetShaderManager ( ShaderManagerVulkan * sm ) {
shaderManagerVulkan_ = sm ;
shaderManager_ = sm ;
}
2017-10-18 12:26:02 +02:00
void FramebufferManagerVulkan : : SetDrawEngine ( DrawEngineVulkan * td ) {
drawEngineVulkan_ = td ;
drawEngine_ = td ;
}
2016-10-09 11:09:30 -07:00
void FramebufferManagerVulkan : : InitDeviceObjects ( ) {
2016-03-28 22:14:04 +02:00
std : : string fs_errors , vs_errors ;
fsBasicTex_ = CompileShaderModule ( vulkan_ , VK_SHADER_STAGE_FRAGMENT_BIT , tex_fs , & fs_errors ) ;
vsBasicTex_ = CompileShaderModule ( vulkan_ , VK_SHADER_STAGE_VERTEX_BIT , tex_vs , & vs_errors ) ;
assert ( fsBasicTex_ ! = VK_NULL_HANDLE ) ;
assert ( vsBasicTex_ ! = VK_NULL_HANDLE ) ;
2016-03-30 23:26:16 +02:00
2016-03-28 22:14:04 +02:00
VkSamplerCreateInfo samp = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO } ;
samp . addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE ;
samp . addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE ;
samp . addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE ;
samp . magFilter = VK_FILTER_NEAREST ;
samp . minFilter = VK_FILTER_NEAREST ;
VkResult res = vkCreateSampler ( vulkan_ - > GetDevice ( ) , & samp , nullptr , & nearestSampler_ ) ;
assert ( res = = VK_SUCCESS ) ;
samp . magFilter = VK_FILTER_LINEAR ;
samp . minFilter = VK_FILTER_LINEAR ;
res = vkCreateSampler ( vulkan_ - > GetDevice ( ) , & samp , nullptr , & linearSampler_ ) ;
assert ( res = = VK_SUCCESS ) ;
2016-01-03 23:09:37 +01:00
}
2016-01-05 21:18:43 +01:00
2016-10-09 11:09:30 -07:00
void FramebufferManagerVulkan : : DestroyDeviceObjects ( ) {
2016-03-22 00:12:41 +01:00
delete drawPixelsTex_ ;
2016-10-09 11:09:30 -07:00
drawPixelsTex_ = nullptr ;
2016-03-28 22:14:04 +02:00
2016-10-09 11:09:30 -07:00
if ( fsBasicTex_ ! = VK_NULL_HANDLE )
vulkan_ - > Delete ( ) . QueueDeleteShaderModule ( fsBasicTex_ ) ;
if ( vsBasicTex_ ! = VK_NULL_HANDLE )
vulkan_ - > Delete ( ) . QueueDeleteShaderModule ( vsBasicTex_ ) ;
2017-11-01 14:18:39 +01:00
if ( stencilFs_ ! = VK_NULL_HANDLE )
vulkan_ - > Delete ( ) . QueueDeleteShaderModule ( stencilFs_ ) ;
if ( stencilVs_ ! = VK_NULL_HANDLE )
vulkan_ - > Delete ( ) . QueueDeleteShaderModule ( stencilVs_ ) ;
2016-03-28 22:14:04 +02:00
2016-10-09 11:09:30 -07:00
if ( linearSampler_ ! = VK_NULL_HANDLE )
vulkan_ - > Delete ( ) . QueueDeleteSampler ( linearSampler_ ) ;
if ( nearestSampler_ ! = VK_NULL_HANDLE )
vulkan_ - > Delete ( ) . QueueDeleteSampler ( nearestSampler_ ) ;
2017-11-01 23:35:06 +01:00
if ( postVs_ )
vulkan_ - > Delete ( ) . QueueDeleteShaderModule ( postVs_ ) ;
if ( postFs_ )
vulkan_ - > Delete ( ) . QueueDeleteShaderModule ( postFs_ ) ;
pipelinePostShader_ = VK_NULL_HANDLE ; // actual pipeline should get destroyed by vulkan2d.
2016-03-22 00:12:41 +01:00
}
2016-03-30 23:26:16 +02:00
void FramebufferManagerVulkan : : NotifyClear ( bool clearColor , bool clearAlpha , bool clearDepth , uint32_t color , float depth ) {
2017-10-30 12:05:08 +01:00
float x , y , w , h ;
CenterDisplayOutputRect ( & x , & y , & w , & h , 480.0f , 272.0f , ( float ) pixelWidth_ , ( float ) pixelHeight_ , ROTATION_LOCKED_HORIZONTAL ) ;
2016-03-22 00:12:41 +01:00
2017-10-30 12:05:08 +01:00
int mask = 0 ;
// The Clear detection takes care of doing a regular draw instead if separate masking
// of color and alpha is needed, so we can just treat them as the same.
if ( clearColor | | clearAlpha )
mask | = Draw : : FBChannel : : FB_COLOR_BIT ;
if ( clearDepth )
mask | = Draw : : FBChannel : : FB_DEPTH_BIT ;
if ( clearAlpha )
mask | = Draw : : FBChannel : : FB_STENCIL_BIT ;
2016-03-30 23:26:16 +02:00
2017-11-18 00:33:20 +01:00
// Note that since the alpha channel and the stencil channel are shared on the PSP,
// when we clear alpha, we also clear stencil to the same value.
draw_ - > Clear ( mask , color , depth , color > > 24 ) ;
2017-10-30 12:05:08 +01:00
if ( clearColor | | clearAlpha ) {
SetColorUpdated ( gstate_c . skipDrawReason ) ;
}
if ( clearDepth ) {
SetDepthUpdated ( ) ;
}
2016-03-22 00:12:41 +01:00
}
2016-01-10 14:24:10 +01:00
void FramebufferManagerVulkan : : Init ( ) {
FramebufferManagerCommon : : Init ( ) ;
// Workaround for upscaling shaders where we force x1 resolution without saving it
2017-04-24 11:58:16 -07:00
Resized ( ) ;
2016-01-10 14:24:10 +01:00
}
2017-03-22 20:56:26 -07:00
void FramebufferManagerVulkan : : MakePixelTexture ( const u8 * srcPixels , GEBufferFormat srcPixelFormat , int srcStride , int width , int height , float & u1 , float & v1 ) {
2017-11-01 00:09:04 +01:00
if ( drawPixelsTex_ ) {
2016-03-28 19:57:42 +02:00
delete drawPixelsTex_ ;
drawPixelsTex_ = nullptr ;
2016-01-10 14:24:10 +01:00
}
2017-08-19 17:32:10 +02:00
VkCommandBuffer initCmd = ( VkCommandBuffer ) draw_ - > GetNativeObject ( Draw : : NativeObject : : INIT_COMMANDBUFFER ) ;
2017-12-03 10:29:41 +01:00
// There's only ever a few of these alive, don't need to stress the allocator with these big ones.
drawPixelsTex_ = new VulkanTexture ( vulkan_ , nullptr ) ;
2017-11-01 01:17:01 +01:00
if ( ! drawPixelsTex_ - > CreateDirect ( initCmd , width , height , 1 , VK_FORMAT_R8G8B8A8_UNORM , VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL , VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT ) ) {
// out of memory?
delete drawPixelsTex_ ;
drawPixelsTex_ = nullptr ;
overrideImageView_ = VK_NULL_HANDLE ;
return ;
}
2017-11-01 00:09:04 +01:00
// Initialize backbuffer texture for DrawPixels
drawPixelsTexFormat_ = srcPixelFormat ;
2016-01-10 14:24:10 +01: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.
2017-10-31 23:49:13 +01:00
// Could also convert directly into the pushbuffer easily.
2016-03-28 22:14:04 +02:00
const uint8_t * data = srcPixels ;
2016-01-10 14:24:10 +01:00
if ( srcPixelFormat ! = GE_FORMAT_8888 | | srcStride ! = width ) {
u32 neededSize = width * height * 4 ;
if ( ! convBuf_ | | convBufSize_ < neededSize ) {
delete [ ] convBuf_ ;
convBuf_ = new u8 [ neededSize ] ;
convBufSize_ = neededSize ;
}
2016-03-30 22:17:40 +02:00
data = convBuf_ ;
2016-01-10 14:24:10 +01:00
for ( int y = 0 ; y < height ; y + + ) {
switch ( srcPixelFormat ) {
case GE_FORMAT_565 :
{
const u16 * src = ( const u16 * ) srcPixels + srcStride * y ;
u8 * dst = convBuf_ + 4 * width * y ;
ConvertRGBA565ToRGBA8888 ( ( u32 * ) dst , src , width ) ;
}
break ;
case GE_FORMAT_5551 :
{
const u16 * src = ( const u16 * ) srcPixels + srcStride * y ;
u8 * dst = convBuf_ + 4 * width * y ;
ConvertRGBA5551ToRGBA8888 ( ( u32 * ) dst , src , width ) ;
}
break ;
case GE_FORMAT_4444 :
{
const u16 * src = ( const u16 * ) srcPixels + srcStride * y ;
u8 * dst = convBuf_ + 4 * width * y ;
ConvertRGBA4444ToRGBA8888 ( ( u32 * ) dst , src , width ) ;
}
break ;
case GE_FORMAT_8888 :
{
const u8 * src = srcPixels + srcStride * 4 * y ;
u8 * dst = convBuf_ + 4 * width * y ;
memcpy ( dst , src , 4 * width ) ;
}
break ;
case GE_FORMAT_INVALID :
_dbg_assert_msg_ ( G3D , false , " Invalid pixelFormat passed to DrawPixels(). " ) ;
break ;
}
}
}
2016-03-28 22:14:04 +02:00
VkBuffer buffer ;
2017-10-30 12:05:08 +01:00
size_t offset = push_ - > Push ( data , width * height * 4 , & buffer ) ;
2017-08-19 17:32:10 +02:00
drawPixelsTex_ - > UploadMip ( initCmd , 0 , width , height , buffer , ( uint32_t ) offset , width ) ;
drawPixelsTex_ - > EndCreate ( initCmd ) ;
2017-05-22 15:29:14 +02:00
overrideImageView_ = drawPixelsTex_ - > GetImageView ( ) ;
2016-01-05 21:18:43 +01:00
}
2017-02-15 13:05:10 +01:00
void FramebufferManagerVulkan : : SetViewport2D ( int x , int y , int w , int h ) {
2017-08-19 17:32:10 +02:00
Draw : : Viewport vp ;
vp . MinDepth = 0.0 ;
vp . MaxDepth = 1.0 ;
vp . TopLeftX = ( float ) x ;
vp . TopLeftY = ( float ) y ;
vp . Width = ( float ) w ;
vp . Height = ( float ) h ;
2017-05-31 23:24:56 -07:00
// Since we're about to override it.
2017-08-19 17:32:10 +02:00
draw_ - > SetViewports ( 1 , & vp ) ;
2017-02-15 13:05:10 +01:00
}
2017-05-31 23:24:56 -07:00
void FramebufferManagerVulkan : : 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 ) {
2016-01-10 14:24:10 +01:00
float texCoords [ 8 ] = {
u0 , v0 ,
u1 , v0 ,
u1 , v1 ,
u0 , v1 ,
} ;
if ( uvRotation ! = ROTATION_LOCKED_HORIZONTAL ) {
float temp [ 8 ] ;
int rotation = 0 ;
switch ( uvRotation ) {
case ROTATION_LOCKED_HORIZONTAL180 : rotation = 4 ; break ;
case ROTATION_LOCKED_VERTICAL : rotation = 2 ; break ;
case ROTATION_LOCKED_VERTICAL180 : rotation = 6 ; break ;
}
for ( int i = 0 ; i < 8 ; i + + ) {
temp [ i ] = texCoords [ ( i + rotation ) & 7 ] ;
}
memcpy ( texCoords , temp , sizeof ( temp ) ) ;
}
2016-03-28 22:14:04 +02:00
Vulkan2D : : Vertex vtx [ 4 ] = {
2017-05-23 21:56:48 +02:00
{ x , y , 0 , texCoords [ 0 ] , texCoords [ 1 ] } ,
{ x + w , y , 0 , texCoords [ 2 ] , texCoords [ 3 ] } ,
{ x , y + h , 0 , texCoords [ 6 ] , texCoords [ 7 ] } ,
{ x + w , y + h , 0 , texCoords [ 4 ] , texCoords [ 5 ] } ,
2016-01-10 14:24:10 +01:00
} ;
float invDestW = 1.0f / ( destW * 0.5f ) ;
float invDestH = 1.0f / ( destH * 0.5f ) ;
for ( int i = 0 ; i < 4 ; i + + ) {
2016-03-28 22:14:04 +02:00
vtx [ i ] . x = vtx [ i ] . x * invDestW - 1.0f ;
vtx [ i ] . y = vtx [ i ] . y * invDestH - 1.0f ;
2016-01-10 14:24:10 +01:00
}
2017-05-21 23:13:53 +02:00
draw_ - > FlushState ( ) ;
// TODO: Should probably use draw_ directly and not go low level
2017-08-19 17:32:10 +02:00
VulkanRenderManager * renderManager = ( VulkanRenderManager * ) draw_ - > GetNativeObject ( Draw : : NativeObject : : RENDER_MANAGER ) ;
2016-03-30 21:17:20 +02:00
2017-10-31 12:02:10 +01:00
VkImageView view = overrideImageView_ ? overrideImageView_ : ( VkImageView ) draw_ - > GetNativeObject ( Draw : : NativeObject : : BOUND_TEXTURE0_IMAGEVIEW ) ;
2017-05-31 23:24:56 -07:00
if ( ( flags & DRAWTEX_KEEP_TEX ) = = 0 )
overrideImageView_ = VK_NULL_HANDLE ;
2017-10-30 12:05:08 +01:00
VkDescriptorSet descSet = vulkan2D_ - > GetDescriptorSet ( view , ( flags & DRAWTEX_LINEAR ) ? linearSampler_ : nearestSampler_ , VK_NULL_HANDLE , VK_NULL_HANDLE ) ;
2016-03-28 22:14:04 +02:00
VkBuffer vbuffer ;
2017-10-30 12:05:08 +01:00
VkDeviceSize offset = push_ - > Push ( vtx , sizeof ( vtx ) , & vbuffer ) ;
2017-10-23 14:53:35 +02:00
renderManager - > BindPipeline ( cur2DPipeline_ ) ;
2017-11-01 23:35:06 +01:00
if ( cur2DPipeline_ = = pipelinePostShader_ ) {
renderManager - > PushConstants ( vulkan2D_ - > GetPipelineLayout ( ) , VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_VERTEX_BIT , 0 , ( int ) sizeof ( postShaderUniforms_ ) , & postShaderUniforms_ ) ;
}
2017-10-30 12:05:08 +01:00
renderManager - > Draw ( vulkan2D_ - > GetPipelineLayout ( ) , descSet , 0 , nullptr , vbuffer , offset , 4 ) ;
2016-01-10 14:24:10 +01:00
}
2017-02-15 16:01:59 +01:00
void FramebufferManagerVulkan : : Bind2DShader ( ) {
2017-05-21 23:13:53 +02:00
VkRenderPass rp = ( VkRenderPass ) draw_ - > GetNativeObject ( Draw : : NativeObject : : COMPATIBLE_RENDERPASS ) ;
2017-10-30 12:05:08 +01:00
cur2DPipeline_ = vulkan2D_ - > GetPipeline ( rp , vsBasicTex_ , fsBasicTex_ ) ;
2017-02-15 16:01:59 +01:00
}
2017-02-15 23:24:25 +01:00
void FramebufferManagerVulkan : : BindPostShader ( const PostShaderUniforms & uniforms ) {
2017-11-01 23:35:06 +01:00
if ( ! pipelinePostShader_ ) {
if ( usePostShader_ ) {
CompilePostShader ( ) ;
}
if ( ! usePostShader_ ) {
SetNumExtraFBOs ( 0 ) ;
Bind2DShader ( ) ;
return ;
} else {
SetNumExtraFBOs ( 1 ) ;
}
}
postShaderUniforms_ = uniforms ;
cur2DPipeline_ = pipelinePostShader_ ;
2017-08-15 10:38:20 +02:00
gstate_c . Dirty ( DIRTY_VERTEXSHADER_STATE ) ;
2017-02-15 23:24:25 +01:00
}
2016-01-10 14:24:10 +01:00
int FramebufferManagerVulkan : : GetLineWidth ( ) {
if ( g_Config . iInternalResolution = = 0 ) {
return std : : max ( 1 , ( int ) ( renderWidth_ / 480 ) ) ;
} else {
return g_Config . iInternalResolution ;
}
}
2017-05-23 23:10:35 +02:00
// This also binds vfb as the current render target.
2016-01-10 14:24:10 +01:00
void FramebufferManagerVulkan : : ReformatFramebufferFrom ( VirtualFramebuffer * vfb , GEBufferFormat old ) {
2017-02-04 18:46:12 +01:00
if ( ! useBufferedRendering_ | | ! vfb - > fbo ) {
2016-01-10 14:24:10 +01:00
return ;
}
2016-03-28 19:57:42 +02:00
2016-01-10 14:24:10 +01:00
// Technically, we should at this point re-interpret the bytes of the old format to the new.
// That might get tricky, and could cause unnecessary slowness in some games.
// For now, we just clear alpha/stencil from 565, which fixes shadow issues in Kingdom Hearts.
// (it uses 565 to write zeros to the buffer, than 4444 to actually render the shadow.)
//
// The best way to do this may ultimately be to create a new FBO (combine with any resize?)
// and blit with a shader to that, then replace the FBO on vfb. Stencil would still be complex
// to exactly reproduce in 4444 and 8888 formats.
if ( old = = GE_FORMAT_565 ) {
2017-12-30 22:52:22 +01:00
draw_ - > BindFramebufferAsRenderTarget ( vfb - > fbo , { Draw : : RPAction : : CLEAR , Draw : : RPAction : : CLEAR , Draw : : RPAction : : CLEAR } ) ;
2017-05-23 23:10:35 +02:00
} else {
2017-12-30 22:52:22 +01:00
draw_ - > BindFramebufferAsRenderTarget ( vfb - > fbo , { Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP } ) ;
2016-01-10 14:24:10 +01:00
}
}
2017-05-23 23:10:35 +02:00
// Except for a missing rebind and silly scissor enables, identical copy of the same function in GPU_GLES - tricky parts are in thin3d.
2016-01-10 14:24:10 +01:00
void FramebufferManagerVulkan : : BlitFramebufferDepth ( VirtualFramebuffer * src , VirtualFramebuffer * dst ) {
2017-05-23 23:10:35 +02:00
if ( g_Config . bDisableSlowFramebufEffects ) {
return ;
}
2016-01-10 14:24:10 +01:00
2017-05-23 23:10:35 +02:00
bool matchingDepthBuffer = src - > z_address = = dst - > z_address & & src - > z_stride ! = 0 & & dst - > z_stride ! = 0 ;
bool matchingSize = src - > width = = dst - > width & & src - > height = = dst - > height ;
bool matchingRenderSize = src - > renderWidth = = dst - > renderWidth & & src - > renderHeight = = dst - > renderHeight ;
2017-10-25 21:56:39 +02:00
if ( matchingDepthBuffer & & matchingRenderSize & & matchingSize ) {
// TODO: Currently, this copies depth AND stencil, which is a problem. See #9740.
2017-05-23 23:10:35 +02:00
draw_ - > CopyFramebufferImage ( src - > fbo , 0 , 0 , 0 , 0 , dst - > fbo , 0 , 0 , 0 , 0 , src - > renderWidth , src - > renderHeight , 1 , Draw : : FB_DEPTH_BIT ) ;
2017-12-24 11:52:15 -08:00
dst - > last_frame_depth_updated = gpuStats . numFlips ;
2017-05-23 23:10:35 +02:00
} else if ( matchingDepthBuffer & & matchingSize ) {
2017-10-25 21:56:39 +02:00
/*
2017-05-23 23:10:35 +02:00
int w = std : : min ( src - > renderWidth , dst - > renderWidth ) ;
int h = std : : min ( src - > renderHeight , dst - > renderHeight ) ;
2017-06-01 20:58:33 -07:00
draw_ - > BlitFramebuffer ( src - > fbo , 0 , 0 , w , h , dst - > fbo , 0 , 0 , w , h , Draw : : FB_DEPTH_BIT , Draw : : FB_BLIT_NEAREST ) ;
2017-10-25 21:56:39 +02:00
*/
2016-03-28 19:57:42 +02:00
}
2016-01-10 14:24:10 +01:00
}
2017-05-22 10:42:40 +02:00
VkImageView FramebufferManagerVulkan : : BindFramebufferAsColorTexture ( int stage , VirtualFramebuffer * framebuffer , int flags ) {
if ( ! framebuffer - > fbo | | ! useBufferedRendering_ ) {
gstate_c . skipDrawReason | = SKIPDRAW_BAD_FB_TEXTURE ;
return VK_NULL_HANDLE ;
}
// 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.
bool skipCopy = ( flags & BINDFBCOLOR_MAY_COPY ) = = 0 ;
if ( GPUStepping : : IsStepping ( ) | | g_Config . bDisableSlowFramebufEffects ) {
skipCopy = true ;
}
// Currently rendering to this framebuffer. Need to make a copy.
if ( ! skipCopy & & framebuffer = = currentRenderVfb_ ) {
// TODO: Maybe merge with bvfbs_? Not sure if those could be packing, and they're created at a different size.
Draw : : Framebuffer * renderCopy = GetTempFBO ( framebuffer - > renderWidth , framebuffer - > renderHeight , ( Draw : : FBColorDepth ) framebuffer - > colorDepth ) ;
if ( renderCopy ) {
VirtualFramebuffer copyInfo = * framebuffer ;
copyInfo . fbo = renderCopy ;
CopyFramebufferForColorTexture ( & copyInfo , framebuffer , flags ) ;
RebindFramebuffer ( ) ;
draw_ - > BindFramebufferAsTexture ( renderCopy , stage , Draw : : FB_COLOR_BIT , 0 ) ;
} else {
draw_ - > BindFramebufferAsTexture ( framebuffer - > fbo , stage , Draw : : FB_COLOR_BIT , 0 ) ;
}
2017-10-31 12:02:10 +01:00
return ( VkImageView ) draw_ - > GetNativeObject ( Draw : : NativeObject : : BOUND_TEXTURE0_IMAGEVIEW ) ;
2017-05-22 10:42:40 +02:00
} else if ( framebuffer ! = currentRenderVfb_ ) {
draw_ - > BindFramebufferAsTexture ( framebuffer - > fbo , stage , Draw : : FB_COLOR_BIT , 0 ) ;
2017-10-31 12:02:10 +01:00
return ( VkImageView ) draw_ - > GetNativeObject ( Draw : : NativeObject : : BOUND_TEXTURE0_IMAGEVIEW ) ;
2017-05-22 10:42:40 +02:00
} else {
2017-11-30 15:40:55 -08:00
ERROR_LOG_REPORT_ONCE ( vulkanSelfTexture , G3D , " Attempting to texture from target (src=%08x / target=%08x / flags=%d) " , framebuffer - > fb_address , currentRenderVfb_ - > fb_address , flags ) ;
2017-05-23 11:12:10 +02:00
// To do this safely in Vulkan, we need to use input attachments.
2017-05-22 10:42:40 +02:00
return VK_NULL_HANDLE ;
}
}
2016-01-10 14:24:10 +01:00
bool FramebufferManagerVulkan : : CreateDownloadTempBuffer ( VirtualFramebuffer * nvfb ) {
2017-10-09 12:43:56 +02:00
nvfb - > colorDepth = Draw : : FBO_8888 ;
2016-01-10 14:24:10 +01:00
2017-11-12 21:14:21 +01:00
nvfb - > fbo = draw_ - > CreateFramebuffer ( { nvfb - > bufferWidth , nvfb - > height , 1 , 1 , true , ( Draw : : FBColorDepth ) nvfb - > colorDepth } ) ;
2016-01-10 14:24:10 +01:00
if ( ! ( nvfb - > fbo ) ) {
2017-03-13 12:32:21 +01:00
ERROR_LOG ( FRAMEBUF , " Error creating FBO! %i x %i " , nvfb - > renderWidth , nvfb - > renderHeight ) ;
2016-01-10 14:24:10 +01:00
return false ;
}
2017-12-30 22:52:22 +01:00
draw_ - > BindFramebufferAsRenderTarget ( nvfb - > fbo , { Draw : : RPAction : : CLEAR , Draw : : RPAction : : CLEAR , Draw : : RPAction : : CLEAR } ) ;
2016-01-10 14:24:10 +01:00
return true ;
}
void FramebufferManagerVulkan : : UpdateDownloadTempBuffer ( VirtualFramebuffer * nvfb ) {
2017-10-09 12:43:56 +02:00
// Nothing to do here.
2016-01-10 14:24:10 +01:00
}
void FramebufferManagerVulkan : : BlitFramebuffer ( VirtualFramebuffer * dst , int dstX , int dstY , VirtualFramebuffer * src , int srcX , int srcY , int w , int h , int bpp ) {
if ( ! dst - > fbo | | ! src - > fbo | | ! useBufferedRendering_ ) {
// This can happen if they recently switched from non-buffered.
2017-05-24 00:45:15 +02:00
if ( useBufferedRendering_ )
2017-12-30 22:52:22 +01:00
draw_ - > BindFramebufferAsRenderTarget ( nullptr , { Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP , Draw : : RPAction : : KEEP } ) ;
2016-01-10 14:24:10 +01:00
return ;
}
2017-12-03 14:49:45 +01: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..
if ( dstX + w > dst - > bufferWidth ) {
w - = dstX + w - dst - > bufferWidth ;
}
if ( dstY + h > dst - > bufferHeight ) {
h - = dstY + h - dst - > bufferHeight ;
}
if ( w = = 0 | | h = = 0 )
return ;
2017-05-24 00:45:15 +02:00
float srcXFactor = ( float ) src - > renderWidth / ( float ) src - > bufferWidth ;
float srcYFactor = ( float ) src - > renderHeight / ( float ) src - > bufferHeight ;
2016-01-10 14:24:10 +01:00
const int srcBpp = src - > format = = GE_FORMAT_8888 ? 4 : 2 ;
if ( srcBpp ! = bpp & & bpp ! = 0 ) {
srcXFactor = ( srcXFactor * bpp ) / srcBpp ;
}
int srcX1 = srcX * srcXFactor ;
int srcX2 = ( srcX + w ) * srcXFactor ;
int srcY1 = srcY * srcYFactor ;
int srcY2 = ( srcY + h ) * srcYFactor ;
2017-05-24 00:45:15 +02:00
float dstXFactor = ( float ) dst - > renderWidth / ( float ) dst - > bufferWidth ;
float dstYFactor = ( float ) dst - > renderHeight / ( float ) dst - > bufferHeight ;
2016-01-10 14:24:10 +01:00
const int dstBpp = dst - > format = = GE_FORMAT_8888 ? 4 : 2 ;
if ( dstBpp ! = bpp & & bpp ! = 0 ) {
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 ;
}
2017-05-24 00:45:15 +02:00
// BlitFramebuffer can clip, but CopyFramebufferImage is more restricted.
2016-03-28 19:57:42 +02:00
// 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 sameDepth = dst - > colorDepth = = src - > colorDepth ;
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 & & sameDepth & & srcInsideBounds & & dstInsideBounds & & ! ( xOverlap & & yOverlap ) ) {
2017-05-24 00:45:15 +02:00
draw_ - > CopyFramebufferImage ( src - > fbo , 0 , srcX1 , srcY1 , 0 , dst - > fbo , 0 , dstX1 , dstY1 , 0 , dstX2 - dstX1 , dstY2 - dstY1 , 1 , Draw : : FB_COLOR_BIT ) ;
2016-01-10 14:24:10 +01:00
} else {
2017-05-24 00:45:15 +02:00
draw_ - > BlitFramebuffer ( src - > fbo , srcX1 , srcY1 , srcX2 , srcY2 , dst - > fbo , dstX1 , dstY1 , dstX2 , dstY2 , Draw : : FB_COLOR_BIT , Draw : : FB_BLIT_NEAREST ) ;
2016-01-10 14:24:10 +01:00
}
}
// TODO: SSE/NEON
// Could also make C fake-simd for 64-bit, two 8888 pixels fit in a register :)
void ConvertFromRGBA8888_Vulkan ( u8 * dst , const u8 * src , u32 dstStride , u32 srcStride , u32 width , u32 height , GEBufferFormat format ) {
// Must skip stride in the cases below. Some games pack data into the cracks, like MotoGP.
const u32 * src32 = ( const u32 * ) src ;
if ( format = = GE_FORMAT_8888 ) {
u32 * dst32 = ( u32 * ) dst ;
if ( src = = dst ) {
return ;
} else {
// Here let's assume they don't intersect
for ( u32 y = 0 ; y < height ; + + y ) {
memcpy ( dst32 , src32 , width * 4 ) ;
src32 + = srcStride ;
dst32 + = dstStride ;
}
}
} else {
// But here it shouldn't matter if they do intersect
u16 * dst16 = ( u16 * ) dst ;
switch ( format ) {
case GE_FORMAT_565 : // BGR 565
for ( u32 y = 0 ; y < height ; + + y ) {
ConvertRGBA8888ToRGB565 ( dst16 , src32 , width ) ;
src32 + = srcStride ;
dst16 + = dstStride ;
}
break ;
case GE_FORMAT_5551 : // ABGR 1555
for ( u32 y = 0 ; y < height ; + + y ) {
ConvertBGRA8888ToRGBA5551 ( dst16 , src32 , width ) ;
src32 + = srcStride ;
dst16 + = dstStride ;
}
break ;
case GE_FORMAT_4444 : // ABGR 4444
for ( u32 y = 0 ; y < height ; + + y ) {
ConvertRGBA8888ToRGBA4444 ( dst16 , src32 , width ) ;
src32 + = srcStride ;
dst16 + = dstStride ;
}
break ;
case GE_FORMAT_8888 :
case GE_FORMAT_INVALID :
// Not possible.
break ;
}
}
}
2016-03-22 00:12:41 +01:00
void FramebufferManagerVulkan : : BeginFrameVulkan ( ) {
BeginFrame ( ) ;
}
2016-01-10 14:24:10 +01:00
void FramebufferManagerVulkan : : EndFrame ( ) {
}
void FramebufferManagerVulkan : : DeviceLost ( ) {
2017-04-13 23:07:21 -07:00
DestroyAllFBOs ( ) ;
2016-10-09 11:09:30 -07:00
DestroyDeviceObjects ( ) ;
}
2017-11-09 16:28:22 +01:00
void FramebufferManagerVulkan : : DeviceRestore ( VulkanContext * vulkan , Draw : : DrawContext * draw ) {
2016-10-09 11:09:30 -07:00
vulkan_ = vulkan ;
2017-11-09 16:28:22 +01:00
draw_ = draw ;
2016-10-09 11:09:30 -07:00
InitDeviceObjects ( ) ;
2016-01-10 14:24:10 +01:00
}
2017-04-13 23:07:21 -07:00
void FramebufferManagerVulkan : : DestroyAllFBOs ( ) {
2016-01-10 14:24:10 +01:00
currentRenderVfb_ = 0 ;
displayFramebuf_ = 0 ;
prevDisplayFramebuf_ = 0 ;
prevPrevDisplayFramebuf_ = 0 ;
for ( size_t i = 0 ; i < vfbs_ . size ( ) ; + + i ) {
VirtualFramebuffer * vfb = vfbs_ [ i ] ;
2017-03-13 12:32:21 +01:00
INFO_LOG ( FRAMEBUF , " Destroying FBO for %08x : %i x %i x %i " , vfb - > fb_address , vfb - > width , vfb - > height , vfb - > format ) ;
2016-01-10 14:24:10 +01:00
DestroyFramebuf ( vfb ) ;
}
vfbs_ . clear ( ) ;
for ( size_t i = 0 ; i < bvfbs_ . size ( ) ; + + i ) {
VirtualFramebuffer * vfb = bvfbs_ [ i ] ;
DestroyFramebuf ( vfb ) ;
}
bvfbs_ . clear ( ) ;
2017-12-01 12:15:55 +01:00
for ( auto it = tempFBOs_ . begin ( ) , end = tempFBOs_ . end ( ) ; it ! = end ; + + it ) {
it - > second . fbo - > Release ( ) ;
}
tempFBOs_ . clear ( ) ;
SetNumExtraFBOs ( 0 ) ;
2016-01-10 14:24:10 +01:00
}
2016-01-05 22:55:47 +01:00
2016-01-10 14:24:10 +01:00
void FramebufferManagerVulkan : : Resized ( ) {
2017-04-24 11:58:16 -07:00
FramebufferManagerCommon : : Resized ( ) ;
if ( UpdateSize ( ) ) {
DestroyAllFBOs ( ) ;
}
2017-11-01 23:35:06 +01:00
// Might have a new post shader - let's compile it.
CompilePostShader ( ) ;
2016-01-06 23:08:26 +01:00
}
2017-11-01 23:35:06 +01:00
void FramebufferManagerVulkan : : CompilePostShader ( ) {
if ( postVs_ ) {
vulkan_ - > Delete ( ) . QueueDeleteShaderModule ( postVs_ ) ;
}
if ( postFs_ ) {
vulkan_ - > Delete ( ) . QueueDeleteShaderModule ( postFs_ ) ;
}
const ShaderInfo * shaderInfo = nullptr ;
if ( g_Config . sPostShaderName = = " Off " ) {
usePostShader_ = false ;
return ;
}
usePostShader_ = false ;
ReloadAllPostShaderInfo ( ) ;
shaderInfo = GetPostShaderInfo ( g_Config . sPostShaderName ) ;
std : : string errorVSX , errorFSX ;
std : : string vsSource ;
std : : string fsSource ;
if ( shaderInfo ) {
postShaderAtOutputResolution_ = shaderInfo - > outputResolution ;
size_t sz ;
char * vs = ( char * ) VFSReadFile ( shaderInfo - > vertexShaderFile . c_str ( ) , & sz ) ;
if ( ! vs )
return ;
char * fs = ( char * ) VFSReadFile ( shaderInfo - > fragmentShaderFile . c_str ( ) , & sz ) ;
if ( ! fs ) {
free ( vs ) ;
return ;
}
std : : string vsSourceGLSL = vs ;
std : : string fsSourceGLSL = fs ;
free ( vs ) ;
free ( fs ) ;
TranslatedShaderMetadata metaVS , metaFS ;
if ( ! TranslateShader ( & vsSource , GLSL_VULKAN , & metaVS , vsSourceGLSL , GLSL_140 , Draw : : ShaderStage : : VERTEX , & errorVSX ) )
return ;
if ( ! TranslateShader ( & fsSource , GLSL_VULKAN , & metaFS , fsSourceGLSL , GLSL_140 , Draw : : ShaderStage : : FRAGMENT , & errorFSX ) )
return ;
} else {
return ;
}
I18NCategory * gr = GetI18NCategory ( " Graphics " ) ;
// TODO: Delete the old pipeline?
std : : string errorVS ;
std : : string errorFS ;
postVs_ = CompileShaderModule ( vulkan_ , VK_SHADER_STAGE_VERTEX_BIT , vsSource . c_str ( ) , & errorVS ) ;
postFs_ = CompileShaderModule ( vulkan_ , VK_SHADER_STAGE_FRAGMENT_BIT , fsSource . c_str ( ) , & errorFS ) ;
VkRenderPass backbufferRP = ( VkRenderPass ) draw_ - > GetNativeObject ( Draw : : NativeObject : : BACKBUFFER_RENDERPASS ) ;
if ( postVs_ & & postFs_ ) {
pipelinePostShader_ = vulkan2D_ - > GetPipeline ( backbufferRP , postVs_ , postFs_ , true , Vulkan2D : : VK2DDepthStencilMode : : NONE ) ;
2017-12-01 13:44:45 +01:00
usePostShader_ = true ;
2017-11-01 23:35:06 +01:00
} else {
ELOG ( " Failed to compile. " ) ;
2017-12-01 16:53:54 +01:00
pipelinePostShader_ = VK_NULL_HANDLE ;
2017-12-01 13:44:45 +01:00
usePostShader_ = false ;
2017-11-01 23:35:06 +01:00
}
2017-11-15 13:18:29 +01:00
}