#define PVR_REGS_FOR_RENDERER // see hw/pvr/pvr_regs.h
/*
Created on: Oct 8, 2019
Copyright 2019 flyinghead
This file is part of Flycast.
Flycast 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, either version 2 of the License, or
(at your option) any later version.
Flycast 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 for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see .
*/
#include
#include "rtt_read.h"
#include "rend/last_picture.h"
#include "drawer.h"
#include "hw/pvr/pvr_mem.h"
/* The sorted passes' triangles and their prepasses', their indices written
* to @out (SortPassesRoom() of them): the main buffer, which the card
* reads them from */
void Drawer::SortTriangles(u32 *out)
{
sortedIndexCount = SortPassesTo(&sortedPasses, &prepassPasses, out, true, true);
}
TileClipping BaseDrawer::SetTileClip(u32 val, vk::Rect2D& clipRect)
{
int rect[4] = {};
TileClipping clipmode = ::GetTileClip(val, matrices.GetViewportMatrix(), rect);
if (clipmode == TileClipping::Outside)
IntersectClipRect(rect, baseScissor.offset.x, baseScissor.offset.y,
(int)baseScissor.extent.width, (int)baseScissor.extent.height);
clipRect.offset.x = rect[0];
clipRect.offset.y = rect[1];
clipRect.extent.width = rect[2];
clipRect.extent.height = rect[3];
return clipmode;
}
void BaseDrawer::SetBaseScissor()
{
bool wide_screen_on = settings.rend.WideScreen && !pvrrc.isRenderFramebuffer
&& !matrices.IsClipped();
if (!wide_screen_on)
{
if (pvrrc.isRenderFramebuffer)
{
baseScissor = vk::Rect2D(vk::Offset2D(0, 0),
vk::Extent2D(640, 480));
}
else
{
float width;
float height;
float min_x;
float min_y;
min_x = xform_x(&matrices.GetScissorMatrix(), pvrrc.fb_X_CLIP.min);
min_y = xform_y(&matrices.GetScissorMatrix(), pvrrc.fb_Y_CLIP.min);
width = xform_w(&matrices.GetScissorMatrix(), pvrrc.fb_X_CLIP.max - pvrrc.fb_X_CLIP.min + 1);
height = xform_h(&matrices.GetScissorMatrix(), pvrrc.fb_Y_CLIP.max - pvrrc.fb_Y_CLIP.min + 1);
if (width < 0)
{
min_x += width;
width = -width;
}
if (height < 0)
{
min_y += height;
height = -height;
}
baseScissor = vk::Rect2D(
vk::Offset2D((u32) std::max(lroundf(min_x), 0L),
(u32) std::max(lroundf(min_y), 0L)),
vk::Extent2D((u32) std::max(lroundf(width), 0L),
(u32) std::max(lroundf(height), 0L)));
}
}
else
{
baseScissor = vk::Rect2D(vk::Offset2D(0, 0),
vk::Extent2D(screen_width, screen_height));
}
currentScissor = { 0, 0, 0, 0 };
}
// Vulkan uses the color values of the first vertex for flat shaded triangle strips.
// On Dreamcast the last vertex is the provoking one so we must copy it onto the first.
void BaseDrawer::SetProvokingVertices()
{
auto setProvokingVertex = [](const List& list) {
for (int i = 0; i < list.used(); i++)
{
const PolyParam& pp = list.head()[i];
if (!pp.pcw.Gouraud && pp.count > 2)
{
for (int i = 0; i < pp.count - 2; i++)
{
Vertex *vertex = &pvrrc.verts.head()[pvrrc.idx.head()[pp.first + i]];
Vertex *lastVertex = &pvrrc.verts.head()[pvrrc.idx.head()[pp.first + i + 2]];
memcpy(vertex->col, lastVertex->col, 4);
memcpy(vertex->vtx_spc, lastVertex->vtx_spc, 4);
memcpy(vertex->col1, lastVertex->col1, 4);
memcpy(vertex->spc1, lastVertex->spc1, 4);
}
}
}
};
setProvokingVertex(pvrrc.global_param_op);
setProvokingVertex(pvrrc.global_param_pt);
setProvokingVertex(pvrrc.global_param_tr);
}
void Drawer::DrawPoly(const vk::CommandBuffer& cmdBuffer, u32 listType, bool sortTriangles, const PolyParam& poly, u32 first, u32 count,
PipelineManager::ShadowPass shadowPass, const vk::Rect2D *within)
{
vk::Rect2D scissorRect;
TileClipping tileClip = SetTileClip(poly.tileclip, scissorRect);
if (within != nullptr)
{
// only the part of it inside this rectangle, and nothing if there is none
const vk::Rect2D& clip = tileClip == TileClipping::Outside ? scissorRect : baseScissor;
const int x0 = std::max(within->offset.x, clip.offset.x);
const int y0 = std::max(within->offset.y, clip.offset.y);
const int x1 = std::min(within->offset.x + (int)within->extent.width, clip.offset.x + (int)clip.extent.width);
const int y1 = std::min(within->offset.y + (int)within->extent.height, clip.offset.y + (int)clip.extent.height);
if (x1 <= x0 || y1 <= y0)
return;
SetScissor(cmdBuffer, vk::Rect2D(vk::Offset2D(x0, y0), vk::Extent2D(x1 - x0, y1 - y0)));
}
else if (tileClip == TileClipping::Outside)
SetScissor(cmdBuffer, scissorRect);
else
SetScissor(cmdBuffer, baseScissor);
// (what the shader multiplies by when it is trilinear: pushed for every such polygon, 1 included)
const bool trilinear = poly.tsp.FilterMode > 1 && poly.pcw.Texture && listType != ListType_Punch_Through && poly.tcw.MipMapped == 1;
float trilinearAlpha = 1.f;
if (trilinear)
{
trilinearAlpha = 0.25 * (poly.tsp.MipMapD & 0x3);
if (poly.tsp.FilterMode == 2)
// Trilinear pass A
trilinearAlpha = 1.0 - trilinearAlpha;
}
bool palette = BaseTextureCacheData::IsGpuHandledPaletted(poly.tsp, poly.tcw);
float palette_index = 0.f;
if (palette)
{
if (poly.tcw.PixelFmt == PixelPal4)
palette_index = (float(poly.tcw.PalSelect << 4) + 0.5f) / 1024.f;
else
palette_index = (float((poly.tcw.PalSelect >> 4) << 8) + 0.5f) / 1024.f;
}
const PsoChoice pso = pipelineManager->GetPipeline(listType, sortTriangles, poly, shadowPass);
if (pso.pipeline == VK_NULL_HANDLE)
return;
// (the general pipeline is told the polygon's key too: pso.h)
if (tileClip == TileClipping::Inside || trilinear || palette || pso.general)
{
struct {
float f[6];
s32 key;
} pushConstants = { {
(float)scissorRect.offset.x,
(float)scissorRect.offset.y,
(float)scissorRect.offset.x + (float)scissorRect.extent.width,
(float)scissorRect.offset.y + (float)scissorRect.extent.height,
trilinearAlpha,
palette_index
}, (s32)(pso.key & ((1u << TRI_SC_BITS) - 1)) };
cmdBuffer.pushConstants(pipelineManager->GetPipelineLayout(), vk::ShaderStageFlagBits::eFragment, 0,
pso.general ? TRI_PUSH_KEY_OFFSET + 4 : TRI_PUSH_KEY_OFFSET, &pushConstants);
for (int i = 0; i < 6; i++)
pushed[i] = pushConstants.f[i];
}
vk::DescriptorSet texSet;
if (poly.pcw.Texture)
texSet = GetCurrentDescSet().SetTexture(poly.texid, poly.tsp);
cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, vk::Pipeline(pso.pipeline));
if (pso.general)
pipelineManager->SetDynamicState(cmdBuffer, pso.key);
if (poly.pcw.Texture)
GetCurrentDescSet().BindPerPolyDescriptorSets(cmdBuffer, texSet);
DrawIndices(cmdBuffer, listType, first, count);
}
// The polygons of a list that take shadows, drawn again where they are in a
// modifier volume: see rend/shadows.h.
void Drawer::DrawShadowed(const vk::CommandBuffer& cmdBuffer, u32 listType, const List& polys, u32 first, u32 last,
const ScreenBounds& area, const vk::Rect2D& scissor)
{
/* (of a frame decoded on the GPU, the GPU has said which groups reach
* into the volumes: the others' second commands draw nothing) */
gpuDraws = GpuDraws::Shadows;
const PolyParam *pp_end = polys.head() + last;
for (const PolyParam *pp = polys.head() + first; pp != pp_end; pp++)
if (CanDrawShadowed(listType, pp) && (pvrrc.gpu || PolyOverlaps(pp, area)))
DrawPoly(cmdBuffer, listType, false, *pp, pp->first, pp->count,
ShadowLaterWins(listType, pp) ? PipelineManager::ShadowPass::LaterWins : PipelineManager::ShadowPass::EarlierWins,
&scissor);
gpuDraws = GpuDraws::Groups;
}
void Drawer::DrawSorted(const vk::CommandBuffer& cmdBuffer, const SortTrigDrawParam *polys, u32 count, bool multipass)
{
/* (a frame decoded on the GPU: its sorted indices are this frame's in
* the decoder's room for them, and name the GPU's vertices) */
const u32 base = pvrrc.gpu ? 0 : pvrrc.idx.used();
if (pvrrc.gpu)
{
if (count == 0)
return;
cmdBuffer.bindIndexBuffer(gpuDraw->sorted, gpuDraw->sortedOffset, vk::IndexType::eUint32);
gpuDraws = GpuDraws::Sorted;
}
for (u32 i = 0; i < count; i++)
DrawPoly(cmdBuffer, ListType_Translucent, true, *polys[i].ppid, base + polys[i].first, polys[i].count);
if (multipass && settings.rend.TranslucentPolygonDepthMask)
{
// Sorted translucent polygons are drawn without writing depth. Where
// a later render pass needs their depth (Cosmic Smash), it is written
// now, for those that do not have Z write disabled.
PsoCull cull(cmdBuffer, pipelineManager->CullSetter());
for (u32 i = 0; i < count; i++)
{
const SortTrigDrawParam& param = polys[i];
if (param.count <= 2 || param.ppid->isp.ZWriteDis)
continue;
vk::Rect2D scissorRect;
SetScissor(cmdBuffer, SetTileClip(param.ppid->tileclip, scissorRect) == TileClipping::Outside ? scissorRect : baseScissor);
cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics,
pipelineManager->GetModifierVolumePipeline(ModVolMode::Depth, param.ppid->isp.CullMode));
cull.Set(PsoCullIndex(param.ppid->isp.CullMode));
cmdBuffer.drawIndexed(param.count, 1, base + param.first, 0, 0);
}
}
if (pvrrc.gpu)
{
cmdBuffer.bindIndexBuffer(gpuDraw->indices, 0, vk::IndexType::eUint32);
gpuDraws = GpuDraws::Groups;
}
}
// The depth prepass of a sorted list: rend/sorter.h
void Drawer::DrawPrepass(const vk::CommandBuffer& cmdBuffer, const SortTrigDrawParam *polys, u32 count)
{
if (count == 0)
return;
// (as DrawSorted(): of a frame decoded on the GPU, the indices are in the decoder's room)
const u32 base = pvrrc.gpu ? 0 : pvrrc.idx.used();
if (pvrrc.gpu)
{
cmdBuffer.bindIndexBuffer(gpuDraw->sorted, gpuDraw->sortedOffset, vk::IndexType::eUint32);
gpuDraws = GpuDraws::Sorted;
}
PsoCull cull(cmdBuffer, pipelineManager->CullSetter());
for (u32 i = 0; i < count; i++)
{
const PolyParam& poly = *polys[i].ppid;
vk::Rect2D scissorRect;
const TileClipping tileClip = SetTileClip(poly.tileclip, scissorRect);
SetScissor(cmdBuffer, tileClip == TileClipping::Outside ? scissorRect : baseScissor);
// what the colour pass's shader has as macros (see FragmentShaderParams)
const bool palette = BaseTextureCacheData::IsGpuHandledPaletted(poly.tsp, poly.tcw);
float palette_index = 0.f;
if (palette)
{
if (poly.tcw.PixelFmt == PixelPal4)
palette_index = (float(poly.tcw.PalSelect << 4) + 0.5f) / 1024.f;
else
palette_index = (float((poly.tcw.PalSelect >> 4) << 8) + 0.5f) / 1024.f;
}
const u32 flags = (tileClip == TileClipping::Inside ? PREPASS_CLIP_INSIDE : 0)
| (poly.tsp.UseAlpha ? PREPASS_USE_ALPHA : 0)
| (palette ? PREPASS_PALETTE : 0)
| (poly.tcw.PixelFmt == PixelBumpMap ? PREPASS_BUMP_MAP : 0)
| (poly.tsp.IgnoreTexA || poly.tcw.PixelFmt == Pixel565 ? PREPASS_IGNORE_TEX_A : 0)
| (poly.tsp.ShadInstr * PREPASS_SHAD_INSTR)
| (poly.tsp.ColorClamp ? PREPASS_CLAMP : 0)
| (poly.tsp.FogCtrl == 3 ? PREPASS_FOG_ALPHA : 0)
| (PrepassAlwaysHides(&poly) ? PREPASS_ALWAYS : 0);
const std::array pushConstants = {
(float)scissorRect.offset.x,
(float)scissorRect.offset.y,
(float)scissorRect.offset.x + (float)scissorRect.extent.width,
(float)scissorRect.offset.y + (float)scissorRect.extent.height,
(float)flags,
palette_index
};
cmdBuffer.pushConstants(pipelineManager->GetPipelineLayout(), vk::ShaderStageFlagBits::eFragment, 0, pushConstants);
vk::DescriptorSet texSet;
if (poly.pcw.Texture)
texSet = GetCurrentDescSet().SetTexture(poly.texid, poly.tsp);
cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipelineManager->GetPrepassPipeline(poly));
cull.Set(PsoCullIndex(poly.isp.CullMode));
if (poly.pcw.Texture)
GetCurrentDescSet().BindPerPolyDescriptorSets(cmdBuffer, texSet);
DrawIndices(cmdBuffer, ListType_Translucent, base + polys[i].first, polys[i].count);
}
cmdBuffer.pushConstants(pipelineManager->GetPipelineLayout(), vk::ShaderStageFlagBits::eFragment, 0, pushed);
if (pvrrc.gpu)
{
cmdBuffer.bindIndexBuffer(gpuDraw->indices, 0, vk::IndexType::eUint32);
gpuDraws = GpuDraws::Groups;
}
}
void Drawer::DrawList(const vk::CommandBuffer& cmdBuffer, u32 listType, bool sortTriangles, const List& polys, u32 first, u32 last)
{
const PolyParam *pp_end = polys.head() + last;
for (const PolyParam *pp = polys.head() + first; pp != pp_end; pp++)
if (pp->count > 2)
DrawPoly(cmdBuffer, listType, sortTriangles, *pp, pp->first, pp->count);
}
void Drawer::DrawModVols(const vk::CommandBuffer& cmdBuffer, int first, int count, const RenderPass& previous_pass, const RenderPass& current_pass,
int render_pass)
{
if (count == 0 || ModVolTriangles() == 0)
return;
vk::Buffer buffer = GetMainBuffer(0)->buffer.get();
BindModVolTriangles(cmdBuffer, buffer, offsets.modVolOffset);
SetScissor(cmdBuffer, baseScissor);
ModifierVolumeParam* params = &pvrrc.global_param_mvo.head()[first];
int mod_base = -1;
vk::Pipeline pipeline;
PsoCull cull(cmdBuffer, pipelineManager->CullSetter());
for (int cmv = 0; cmv < count; cmv++)
{
ModifierVolumeParam& param = params[cmv];
u32 mvFirst, mvCount;
mv_clip_range_of(pvrrc, pvrrc.global_param_mvo.head(), param, mvFirst, mvCount);
if (mvCount == 0)
continue;
u32 mv_mode = param.isp.DepthMode;
// clipped like a polygon, to the rectangle it was sent under if it asked to be
{
vk::Rect2D clipRect;
if (SetTileClip(param.tileclip, clipRect) == TileClipping::Outside)
SetScissor(cmdBuffer, clipRect);
else
SetScissor(cmdBuffer, baseScissor);
}
if (mod_base == -1)
mod_base = mvFirst;
if (!param.isp.VolumeLast && mv_mode > 0)
pipeline = pipelineManager->GetModifierVolumePipeline(ModVolMode::Or, param.isp.CullMode); // OR'ing (open volume or quad)
else
pipeline = pipelineManager->GetModifierVolumePipeline(ModVolMode::Xor, param.isp.CullMode); // XOR'ing (closed volume)
cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
cull.Set(PsoCullIndex(param.isp.CullMode));
cmdBuffer.draw(mvCount * 3, 1, mvFirst * 3, 0);
if (mv_mode == 1 || mv_mode == 2)
{
// Sum the area
pipeline = pipelineManager->GetModifierVolumePipeline(mv_mode == 1 ? ModVolMode::Inclusion : ModVolMode::Exclusion, param.isp.CullMode);
cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
cull.Set(PsoCullIndex(param.isp.CullMode));
cmdBuffer.draw((mvFirst + mvCount - mod_base) * 3, 1, mod_base * 3, 0);
mod_base = -1;
}
}
SetScissor(cmdBuffer, baseScissor);
BindVertices(cmdBuffer);
/* The polygons that take shadows, again, where they are in a volume.
* Of a frame decoded on the GPU, the volumes' bounds are the header
* pass's, from their blocks (TA_GPU_SHADOWS) */
const f32 *ga = pvrrc.gpu_shadow_area[render_pass < 10 ? render_pass : 0];
const ScreenBounds area = pvrrc.gpu ? ScreenBounds{ ga[0], ga[1], ga[2], ga[3] } : ModVolBounds(first, count);
if (!area.empty())
{
int rect[4];
BoundsToScissor(area, matrices.GetViewportMatrix(), rect);
const vk::Rect2D scissor(vk::Offset2D(rect[0], rect[1]), vk::Extent2D(rect[2], rect[3]));
DrawShadowed(cmdBuffer, ListType_Opaque, pvrrc.global_param_op, previous_pass.op_count, current_pass.op_count, area, scissor);
DrawShadowed(cmdBuffer, ListType_Punch_Through, pvrrc.global_param_pt, previous_pass.pt_count, current_pass.pt_count, area, scissor);
SetScissor(cmdBuffer, baseScissor);
}
// What is left: darkened as it is
// (all six: the polygon shaders' block is six floats, and what is not pushed is not anything in particular)
const std::array pushConstants = { 1 - FPU_SHAD_SCALE.scale_factor / 256.f, 0, 0, 0, 0, 0 };
cmdBuffer.pushConstants(pipelineManager->GetPipelineLayout(), vk::ShaderStageFlagBits::eFragment, 0, pushConstants);
pushed = pushConstants;
pipeline = pipelineManager->GetModifierVolumePipeline(ModVolMode::Final, 0);
cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
cmdBuffer.drawIndexed(4, 1, 0, 0, 0);
}
/* The main buffer of this render, laid out - the vertices, the volumes'
* triangles, the indices, room for the sorted passes' indices after those,
* and the fragment uniforms - with the sorted passes sorted into it */
BufferData *Drawer::SortIntoMainBuffer()
{
/* (a frame decoded on the GPU has its geometry there, and is sorted
* there: TA_GPU_SORT; one the parse wrote where the card reads it has
* its geometry there, and is sorted after its indices) */
const u32 sortRoom = pvrrc.gpu ? 0 : SortPassesRoom() * sizeof(u32);
const bool here = UseParsed(sortRoom);
const u32 vertexBytes = pvrrc.gpu || here ? 0 : pvrrc.verts.bytes();
const u32 modVolBytes = ModVolsOnCpu() && !here ? ModVolList().bytes() : 0;
const u32 indexBytes = pvrrc.gpu || here ? 0 : pvrrc.idx.bytes();
const u32 sortedBytes = here ? 0 : sortRoom;
offsets.modVolOffset = vertexBytes + align(vertexBytes, 4);
offsets.indexOffset = offsets.modVolOffset + modVolBytes + align(offsets.modVolOffset + modVolBytes, 4);
const u32 indexSize = indexBytes + sortedBytes;
offsets.fragmentUniformOffset = offsets.indexOffset + indexSize
+ align(offsets.indexOffset + indexSize, std::max(4, (int)GetContext()->GetUniformBufferAlignment()));
BufferData *buffer = GetMainBuffer(offsets.fragmentUniformOffset + sizeof(FragmentShaderUniforms));
SetGeometry(buffer->buffer.get());
if (pvrrc.gpu)
sortedIndexCount = 0;
else if (here)
{
offsets.indexOffset = parsed.indices;
SortTriangles(pvrrc.idx.cur());
}
else
SortTriangles((u32 *)((u8 *)buffer->MapMemory() + offsets.indexOffset + indexBytes));
return buffer;
}
/* ...and the rest of it */
void Drawer::UploadMainBuffer(BufferData *buffer, const FragmentShaderUniforms& fragmentUniforms)
{
// (not one the parse wrote where the card reads it: geometryParsed)
if (!pvrrc.gpu && !geometryParsed)
{
buffer->upload(pvrrc.verts.bytes(), pvrrc.verts.head(), 0);
buffer->upload(pvrrc.idx.bytes(), pvrrc.idx.head(), offsets.indexOffset);
}
if (ModVolsOnCpu() && !geometryParsed)
buffer->upload(ModVolList().bytes(), ModVolList().head(), offsets.modVolOffset);
buffer->upload(sizeof(fragmentUniforms), &fragmentUniforms, offsets.fragmentUniformOffset);
}
bool Drawer::Draw(const Texture *fogTexture, const Texture *paletteTexture)
{
currentScissor = vk::Rect2D();
/* (which works the matrices out for this render, and writes the vertex
* uniforms from them: taken before it, they were the last render's - of
* two renders to textures of different sizes in one frame, each was
* drawn with the other's, at the wrong scale) */
vk::CommandBuffer cmdBuffer = BeginRenderPass();
FragmentShaderUniforms fragUniforms = MakeFragmentUniforms();
fragUniforms.shade_scale_factor = FPU_SHAD_SCALE.scale_factor / 256.f;
/* Sorted where the card reads the indices (this frame's main buffer:
* the render pass is begun), from the vertices as the game sent them.
* (A frame decoded on the GPU has its sorted passes sorted already, and
* their indices went straight to the decoder's room for them when the
* decode was recorded: see TA_GPU_SORT.) */
BufferData *mainBuffer = SortIntoMainBuffer();
// (the GPU's decode has done it to a frame it made)
if (!pvrrc.gpu)
SetProvokingVertices();
// Upload vertex and index buffers
UploadMainBuffer(mainBuffer, fragUniforms);
// Update per-frame descriptor set and bind it
GetCurrentDescSet().UpdateUniforms(vertexUniformBuffer, GetMainBuffer(0)->buffer.get(), offsets.fragmentUniformOffset,
fogTexture->GetImageView(), paletteTexture->GetImageView());
GetCurrentDescSet().BindPerFrameDescriptorSets(cmdBuffer);
// Reset per-poly descriptor set pool
GetCurrentDescSet().Reset();
// Bind vertex and index buffers
BindGeometry(cmdBuffer, offsets.indexOffset);
// Make sure to push constants even if not used
const std::array pushConstants = { 0, 0, 0, 0, 0, 0 };
cmdBuffer.pushConstants(pipelineManager->GetPipelineLayout(), vk::ShaderStageFlagBits::eFragment, 0, pushConstants);
pushed = pushConstants;
RenderPass previous_pass = {};
for (int render_pass = 0; render_pass < pvrrc.render_passes.used(); render_pass++)
{
const RenderPass& current_pass = pvrrc.render_passes.head()[render_pass];
DEBUG_LOG(RENDERER, "Render pass %d OP %d PT %d TR %d MV %d autosort %d", render_pass + 1,
current_pass.op_count - previous_pass.op_count,
current_pass.pt_count - previous_pass.pt_count,
current_pass.tr_count - previous_pass.tr_count,
current_pass.mvo_count - previous_pass.mvo_count, current_pass.autosort);
DrawList(cmdBuffer, ListType_Opaque, false, pvrrc.global_param_op, previous_pass.op_count, current_pass.op_count);
DrawList(cmdBuffer, ListType_Punch_Through, false, pvrrc.global_param_pt, previous_pass.pt_count, current_pass.pt_count);
DrawModVols(cmdBuffer, previous_pass.mvo_count, current_pass.mvo_count - previous_pass.mvo_count, previous_pass, current_pass,
render_pass);
if (current_pass.autosort)
{
// (a frame decoded on the GPU with a sorted pass was sorted by triangle: TA_GPU_SORT)
if (pvrrc.gpu)
{
u32 n;
const SortTrigDrawParam *prepass = ta_gpu_prepass(render_pass, &n);
DrawPrepass(cmdBuffer, prepass, n);
const SortTrigDrawParam *sorted = ta_gpu_sorted(render_pass, &n);
DrawSorted(cmdBuffer, sorted, n, render_pass < pvrrc.render_passes.used() - 1);
}
else if (!settings.pvr.Emulation.AlphaSortMode)
{
u32 n;
const SortTrigDrawParam *draws = SortPassDraws(&prepassPasses, render_pass, &n);
DrawPrepass(cmdBuffer, draws, n);
draws = SortPassDraws(&sortedPasses, render_pass, &n);
DrawSorted(cmdBuffer, draws, n, render_pass < pvrrc.render_passes.used() - 1);
}
else
{
u32 n;
const SortTrigDrawParam *draws = SortPassDraws(&prepassPasses, render_pass, &n);
DrawPrepass(cmdBuffer, draws, n);
SortPParams(previous_pass.tr_count, current_pass.tr_count - previous_pass.tr_count);
DrawList(cmdBuffer, ListType_Translucent, true, pvrrc.global_param_tr, previous_pass.tr_count, current_pass.tr_count);
}
}
else
DrawList(cmdBuffer, ListType_Translucent, false, pvrrc.global_param_tr, previous_pass.tr_count, current_pass.tr_count);
previous_pass = current_pass;
}
// SSAA: the samples resolved into the picture, in the last subpass
if (samples > 1)
{
cmdBuffer.nextSubpass(vk::SubpassContents::eInline);
ssaaResolve.Draw(cmdBuffer, ResolveExtent());
}
return !pvrrc.isRTT;
}
void TextureDrawer::Init(SamplerManager *samplerManager, ShaderManager *shaderManager, TextureCache *textureCache, u32 samples)
{
if (!rttPipelineManager)
rttPipelineManager = std::unique_ptr(new RttPipelineManager());
rttPipelineManager->Init(shaderManager, samples);
Drawer::Init(samplerManager, rttPipelineManager.get());
this->textureCache = textureCache;
if (samples != this->samples)
{
// (the depth attachment is of the other kind: one of the right kind is made below or when first needed)
if (depthAttachment && commandPool != nullptr)
commandPool->DeferDelete(std::move(depthAttachment));
depthAttachment.reset();
if (samplesAttachment && commandPool != nullptr)
commandPool->DeferDelete(std::move(samplesAttachment));
samplesAttachment.reset();
this->samples = samples;
}
/* The attachments are made now, never while the game runs: SSAA's, as
* large as a render to a texture can be (where SSAA is done to them,
* memory made only if it is needed backs them); the depth is the
* screen's, or one of its own (ShareDepth()). And a framebuffer's
* place for each image. */
if (samples > 1)
{
if (!samplesAttachment || samplesAttachment->getExtent().width < MaxSide())
MakeSsaaAttachments(MaxSide());
ssaaResolve.Init(rttPipelineManager->GetRenderPass(), 1, samples, samplesAttachment->GetImageView());
}
else
ssaaResolve.Term();
framebuffers.resize(GetContext()->GetSwapChainSize());
}
void TextureDrawer::ShareDepth(const ScreenDrawer *screen)
{
this->screen = screen;
if (samples > 1)
return;
const FramebufferAttachment *shared = screen != nullptr ? screen->SharedDepth() : nullptr;
if (shared != nullptr)
{
// (its own goes once the frames that may use it are done)
if (depthAttachment && commandPool != nullptr)
commandPool->DeferDelete(std::move(depthAttachment));
depthAttachment.reset();
return;
}
const vk::Extent2D common = CommonRender();
if (!depthAttachment || depthAttachment->getExtent().width < common.width || depthAttachment->getExtent().height < common.height)
{
if (depthAttachment && commandPool != nullptr)
commandPool->DeferDelete(std::move(depthAttachment));
depthAttachment = std::unique_ptr(new FramebufferAttachment(GetContext()->GetPhysicalDevice(),
GetContext()->GetDevice()));
depthAttachment->Init(common.width, common.height, GetContext()->GetDepthFormat(),
vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eTransientAttachment);
}
}
void TextureDrawer::MakeSsaaAttachments(u32 side)
{
VulkanContext *context = GetContext();
if (depthAttachment && commandPool != nullptr)
commandPool->DeferDelete(std::move(depthAttachment));
if (samplesAttachment && commandPool != nullptr)
commandPool->DeferDelete(std::move(samplesAttachment));
const vk::SampleCountFlagBits n = (vk::SampleCountFlagBits)samples;
depthAttachment = std::unique_ptr(new FramebufferAttachment(context->GetPhysicalDevice(), context->GetDevice()));
depthAttachment->Init(side, side, context->GetDepthFormat(),
vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eTransientAttachment, true, n);
samplesAttachment = std::unique_ptr(new FramebufferAttachment(context->GetPhysicalDevice(), context->GetDevice()));
samplesAttachment->Init(side, side, vk::Format::eR8G8B8A8Unorm,
vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eInputAttachment | vk::ImageUsageFlagBits::eTransientAttachment,
false, n);
if (ssaaResolve.Ready() && commandPool != nullptr)
ssaaResolve.SetSamplesView(samplesAttachment->GetImageView(), commandPool);
}
vk::CommandBuffer TextureDrawer::BeginRenderPass()
{
DEBUG_LOG(RENDERER, "RenderToTexture packmode=%d stride=%d - %d,%d -> %d,%d @ %08x", FB_W_CTRL.fb_packmode, FB_W_LINESTRIDE.stride * 8,
FB_X_CLIP.min, FB_Y_CLIP.min, FB_X_CLIP.max, FB_Y_CLIP.max, FB_W_SOF1 & VRAM_MASK);
matrices.CalcMatrices(&pvrrc);
textureAddr = FB_W_SOF1 & VRAM_MASK;
// the frame from its first pixel, as far as the pixel clip goes (see TransformMatrix)
u32 origWidth = pvrrc.fb_X_CLIP.max + 1;
u32 origHeight = pvrrc.fb_Y_CLIP.max + 1;
const u32 clipX = pvrrc.fb_X_CLIP.min, clipY = pvrrc.fb_Y_CLIP.min;
const u32 clipWidth = origWidth - clipX, clipHeight = origHeight - clipY;
u32 upscaledWidth = origWidth;
u32 upscaledHeight = origHeight;
u32 heightPow2 = 8;
while (heightPow2 < upscaledHeight)
heightPow2 *= 2;
u32 widthPow2 = 8;
while (widthPow2 < upscaledWidth)
widthPow2 *= 2;
if (settings.rend.RenderToTextureUpscale > 1)
{
upscaledWidth *= settings.rend.RenderToTextureUpscale;
upscaledHeight *= settings.rend.RenderToTextureUpscale;
widthPow2 *= settings.rend.RenderToTextureUpscale;
heightPow2 *= settings.rend.RenderToTextureUpscale;
}
VulkanContext *context = GetContext();
vk::Device device = context->GetDevice();
NewImage();
vk::CommandBuffer commandBuffer = commandPool->Allocate();
commandBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit));
vertexUniformBuffer = WriteVertexUniforms(commandBuffer, GetCurrentImage());
if (samples > 1)
{
/* (SSAA's attachments were made as large as a texture rendered to
* can be; a pixel clip beyond the texture's largest size makes one
* larger, which is the only time they are made while a game runs) */
const u32 side = std::max(widthPow2, heightPow2);
if (side > samplesAttachment->getExtent().width)
{
WARN_LOG(RENDERER, "Render to a texture of %u x %u: SSAA's attachments made larger", widthPow2, heightPow2);
MakeSsaaAttachments(side);
}
}
/* What the render draws: its own pixels, the texture's from the first;
* with SSAA all of the texture, its attachments being that large */
const u32 areaWidth = samples > 1 ? widthPow2 : upscaledWidth;
const u32 areaHeight = samples > 1 ? heightPow2 : upscaledHeight;
/* The depth: the screen's when it is large enough - drawn into by the
* screen's frames and this one by one, on the same queue, each
* clearing it first: the render passes' dependencies on the depth
* before them are those of one frame after another -, or this
* drawer's own; which a render larger than both has made for it. */
const FramebufferAttachment *depth = nullptr;
if (samples > 1)
depth = depthAttachment.get();
else
{
const FramebufferAttachment *shared = screen != nullptr ? screen->SharedDepth() : nullptr;
if (shared != nullptr && areaWidth <= shared->getExtent().width && areaHeight <= shared->getExtent().height)
{
depth = shared;
drewShared++;
}
else
{
drewOwn++;
if (!depthAttachment || areaWidth > depthAttachment->getExtent().width || areaHeight > depthAttachment->getExtent().height)
{
if (!saidLarger)
{
saidLarger = true;
WARN_LOG(RENDERER, "Render to a texture of %u x %u: a depth made for it", areaWidth, areaHeight);
}
const u32 w = depthAttachment ? std::max(areaWidth, depthAttachment->getExtent().width) : areaWidth;
const u32 h = depthAttachment ? std::max(areaHeight, depthAttachment->getExtent().height) : areaHeight;
if (depthAttachment)
commandPool->DeferDelete(std::move(depthAttachment));
depthAttachment = std::unique_ptr(new FramebufferAttachment(context->GetPhysicalDevice(), device));
depthAttachment->Init(w, h, GetContext()->GetDepthFormat(),
vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eTransientAttachment);
}
depth = depthAttachment.get();
}
}
vk::Image colorImage;
vk::ImageView colorImageView;
vk::ImageLayout colorImageCurrentLayout;
/* what an earlier render left waiting in this memory, while its picture is still what it was: see rtt_read.h */
vk_rtt_supersede(textureAddr, clipX, clipY, clipWidth, clipHeight);
// TexAddr : fb_rtt.TexAddr, Reserved : 0, StrideSel : 0, ScanOrder : 1
TCW tcw = { { textureAddr >> 3, 0, 0, 1 } };
switch (FB_W_CTRL.fb_packmode) {
case 0:
case 3:
tcw.PixelFmt = Pixel1555;
break;
case 1:
tcw.PixelFmt = Pixel565;
break;
case 2:
tcw.PixelFmt = Pixel4444;
break;
}
TSP tsp = { 0 };
for (tsp.TexU = 0; tsp.TexU <= 7 && (8u << tsp.TexU) < origWidth; tsp.TexU++);
for (tsp.TexV = 0; tsp.TexV <= 7 && (8u << tsp.TexV) < origHeight; tsp.TexV++);
texture = textureCache->getTextureCacheData(tsp, tcw);
if (texture->IsNew())
{
texture->Create();
texture->SetPhysicalDevice(GetContext()->GetPhysicalDevice());
texture->SetDevice(device);
}
else if (textureCache->IsInFlight(texture))
{
texture->KeepReadOnlyView();
textureCache->DestroyLater(texture);
}
textureCache->SetInFlight(texture);
/* A texture the game uploaded can become one it renders to, at the
* same address, the same size and in this format: its image was not
* made to be rendered to, nor to be copied from, and is made again. */
const vk::ImageUsageFlags renderTargetUsage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled
| vk::ImageUsageFlagBits::eTransferSrc; // read back when the game touches it in video memory
if (texture->format != vk::Format::eR8G8B8A8Unorm || texture->extent.width != widthPow2 || texture->extent.height != heightPow2
|| (texture->imageUsage & renderTargetUsage) != renderTargetUsage)
{
texture->extent = vk::Extent2D(widthPow2, heightPow2);
texture->format = vk::Format::eR8G8B8A8Unorm;
texture->needsStaging = true;
texture->CreateRenderTarget(renderTargetUsage);
colorImageCurrentLayout = vk::ImageLayout::eUndefined;
}
else
{
colorImageCurrentLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
}
colorImage = texture->GetImage();
colorImageView = texture->GetImageView();
width = areaWidth;
height = areaHeight;
if (samples == 1)
texture->ClearOutside(commandBuffer, areaWidth, areaHeight, colorImageCurrentLayout);
setImageLayout(commandBuffer, colorImage, vk::Format::eR8G8B8A8Unorm, 1, colorImageCurrentLayout, vk::ImageLayout::eColorAttachmentOptimal);
// (with SSAA: the texture, then the samples' colour and depth)
vk::ImageView imageViews[] = {
colorImageView,
samples > 1 ? samplesAttachment->GetImageView() : depth->GetImageView(),
depth->GetImageView(),
};
const u64 viewSerials[] = {
texture->GetViewSerial(),
samples > 1 ? samplesAttachment->GetViewSerial() : depth->GetViewSerial(),
depth->GetViewSerial(),
};
/* (made again only for other attachments: a texture rendered to every
* frame is the same one) */
framebuffers.resize(GetContext()->GetSwapChainSize());
const vk::Framebuffer framebuffer = framebuffers[GetCurrentImage()].Get(rttPipelineManager->GetRenderPass(),
samples > 1 ? 3 : 2, imageViews, viewSerials, width, height);
const vk::ClearValue clear_colors[] = { vk::ClearColorValue(std::array { 0.f, 0.f, 0.f, 1.f }), vk::ClearDepthStencilValue { 0.f, 0 } };
const vk::ClearValue ssaa_clear_colors[] = { clear_colors[0], clear_colors[0], clear_colors[1] };
commandBuffer.beginRenderPass(vk::RenderPassBeginInfo(rttPipelineManager->GetRenderPass(), framebuffer,
vk::Rect2D( { 0, 0 }, { width, height }), samples > 1 ? 3 : 2, samples > 1 ? ssaa_clear_colors : clear_colors),
vk::SubpassContents::eInline);
commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, (float)upscaledWidth, (float)upscaledHeight, 0.0f, 1.0f));
{
// only the pixel clip is written
const u32 scale = settings.rend.RenderToTextureUpscale > 1 ? settings.rend.RenderToTextureUpscale : 1;
baseScissor = vk::Rect2D(vk::Offset2D(clipX * scale, clipY * scale), vk::Extent2D(clipWidth * scale, clipHeight * scale));
}
commandBuffer.setScissor(0, baseScissor);
currentCommandBuffer = commandBuffer;
return commandBuffer;
}
void TextureDrawer::EndRenderPass()
{
currentCommandBuffer.endRenderPass();
currentCommandBuffer.end();
currentCommandBuffer = nullptr;
commandPool->EndFrame();
texture->dirty = 0;
texture->stride_read = 0; // (rendered, not read)
texture->made_type = 0;
libCore_vramlock_Lock(texture->sa_tex, texture->sa + texture->size - 1, texture);
/* video memory gets it when something first touches it there */
vk_rtt_watch(texture, textureAddr, pvrrc.fb_X_CLIP.min, pvrrc.fb_Y_CLIP.min,
pvrrc.fb_X_CLIP.max - pvrrc.fb_X_CLIP.min + 1, pvrrc.fb_Y_CLIP.max - pvrrc.fb_Y_CLIP.min + 1);
}
void ScreenDrawer::Init(SamplerManager *samplerManager, ShaderManager *shaderManager, u32 samples)
{
this->shaderManager = shaderManager;
/* (a change of SSAA is one of size: everything made for the size is
* made again; so is one of the write-out, whose pictures are made for
* it to write) */
// (the upscaler wants the depth kept, which SSAA's samples are not)
const bool upscaled = upscaler_active() && samples == 1;
const bool remade = viewport != GetContext()->GetViewPort() || samples != this->samples
|| storagePictures != VulkanWriteOut::Wanted() || upscaled != this->upscaled;
if (remade)
{
/* Not destroyed on the spot: the last picture was handed to the
* frontend, which shows it again for as long as no new one comes -
* and may, before the first frame at the new size is drawn. They
* go when the frames that could still use them have gone by.
* (Upstream: a crash starting the 240p test suite.) */
if (commandPool != nullptr)
{
/* The image with the last frame does not go with the rest. A
* game that draws part of the screen has the rest from the
* last picture, and this is it: the next render starts from it
* as it does from one kept across a new context, at whatever
* size the screen now is - and here nothing is read back or
* loaded again. It goes after the next frame. */
if (carriedPicture)
commandPool->DeferDelete(std::move(carriedPicture));
if (havePicture && (size_t)GetCurrentImage() < colorAttachments.size() && colorAttachments[GetCurrentImage()])
{
carriedPicture = std::move(colorAttachments[GetCurrentImage()]);
restoredPicture = carriedPicture->GetImageView();
}
for (auto& framebuffer : framebuffers)
if (framebuffer)
commandPool->DeferDelete(std::unique_ptr(new vk::UniqueFramebuffer(std::move(framebuffer))));
for (auto& attachment : colorAttachments)
if (attachment)
commandPool->DeferDelete(std::move(attachment));
for (auto& attachment : hdrAttachments)
if (attachment)
commandPool->DeferDelete(std::move(attachment));
if (depthAttachment)
commandPool->DeferDelete(std::move(depthAttachment));
if (samplesAttachment)
commandPool->DeferDelete(std::move(samplesAttachment));
for (auto& attachment : depthAttachments)
if (attachment)
commandPool->DeferDelete(std::move(attachment));
motionPass.Term(commandPool);
}
framebuffers.clear();
colorAttachments.clear();
hdrAttachments.clear();
depthAttachment.reset();
samplesAttachment.reset();
depthAttachments.clear();
havePicture = false;
if (samples != this->samples || upscaled != this->upscaled)
// (Resize() waited for the frames on the GPU: nothing uses it)
renderPass.reset();
this->samples = samples;
this->upscaled = upscaled;
storagePictures = VulkanWriteOut::Wanted();
upscaler_reset();
}
viewport = GetContext()->GetViewPort();
const vk::SampleCountFlagBits n = (vk::SampleCountFlagBits)samples;
if (!depthAttachment && !upscaled)
{
/* (without SSAA, renders to textures draw into it too: as large as
* they need, TextureDrawer::ShareExtent()) */
const vk::Extent2D depthSize = samples == 1 ? TextureDrawer::ShareExtent(viewport) : viewport;
depthAttachment = std::unique_ptr(
new FramebufferAttachment(GetContext()->GetPhysicalDevice(), GetContext()->GetDevice()));
depthAttachment->Init(depthSize.width, depthSize.height, GetContext()->GetDepthFormat(),
vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eTransientAttachment, true, n);
}
// SSAA: the samples' colour, which the last subpass resolves into the picture
if (samples > 1 && !samplesAttachment)
{
samplesAttachment = std::unique_ptr(
new FramebufferAttachment(GetContext()->GetPhysicalDevice(), GetContext()->GetDevice()));
samplesAttachment->Init(viewport.width, viewport.height, GetContext()->GetColorFormat(),
vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eInputAttachment
| vk::ImageUsageFlagBits::eTransientAttachment, false, n);
}
/* The HDR output: a last subpass encodes the picture, its input, into
* the HDR image, the frontend's (hdr_encode_vk.h) */
const bool hdr = GetContext()->HdrActive();
if (!renderPass && samples > 1)
{
renderPass = MakeSsaaRenderPass(GetContext()->GetColorFormat(), samples, vk::ImageLayout::eShaderReadOnlyOptimal,
hdr ? GetContext()->GetHdrFormat() : vk::Format::eUndefined);
hdrPipeline.reset();
}
if (!renderPass)
{
hdrPipeline.reset();
vk::AttachmentDescription attachmentDescriptions[] = {
// Color attachment
vk::AttachmentDescription(vk::AttachmentDescriptionFlags(), GetContext()->GetColorFormat(), vk::SampleCountFlagBits::e1,
vk::AttachmentLoadOp::eClear, vk::AttachmentStoreOp::eStore,
vk::AttachmentLoadOp::eDontCare, vk::AttachmentStoreOp::eDontCare,
vk::ImageLayout::eUndefined, vk::ImageLayout::eShaderReadOnlyOptimal),
// Depth attachment (kept for the upscaler, when there is one)
vk::AttachmentDescription(vk::AttachmentDescriptionFlags(), GetContext()->GetDepthFormat(), vk::SampleCountFlagBits::e1,
vk::AttachmentLoadOp::eClear, upscaled ? vk::AttachmentStoreOp::eStore : vk::AttachmentStoreOp::eDontCare,
vk::AttachmentLoadOp::eClear, vk::AttachmentStoreOp::eDontCare,
vk::ImageLayout::eUndefined, vk::ImageLayout::eDepthStencilAttachmentOptimal),
// The HDR output's image
vk::AttachmentDescription(vk::AttachmentDescriptionFlags(), GetContext()->GetHdrFormat(), vk::SampleCountFlagBits::e1,
vk::AttachmentLoadOp::eDontCare, vk::AttachmentStoreOp::eStore,
vk::AttachmentLoadOp::eDontCare, vk::AttachmentStoreOp::eDontCare,
vk::ImageLayout::eUndefined, vk::ImageLayout::eShaderReadOnlyOptimal),
};
vk::AttachmentReference colorReference(0, vk::ImageLayout::eColorAttachmentOptimal);
vk::AttachmentReference depthReference(1, vk::ImageLayout::eDepthStencilAttachmentOptimal);
vk::AttachmentReference pictureInput(0, vk::ImageLayout::eShaderReadOnlyOptimal);
vk::AttachmentReference hdrReference(2, vk::ImageLayout::eColorAttachmentOptimal);
vk::SubpassDescription subpasses[] = {
vk::SubpassDescription(vk::SubpassDescriptionFlags(), vk::PipelineBindPoint::eGraphics,
0, nullptr,
1, &colorReference,
nullptr,
&depthReference),
// the HDR output: the picture encoded where it is
vk::SubpassDescription(vk::SubpassDescriptionFlags(), vk::PipelineBindPoint::eGraphics,
1, &pictureInput,
1, &hdrReference,
nullptr,
nullptr),
};
std::vector dependencies;
/* (the frontend reads the picture anywhere, from a render pass of its
* own or a copy: not by region, or it reads tiles not drawn yet) */
dependencies.emplace_back(0, VK_SUBPASS_EXTERNAL, vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::PipelineStageFlagBits::eFragmentShader | vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eTransfer,
vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferRead);
/* The depth attachment is the same one every frame: the frame before
* has to be done with it before this one clears it. Nothing said
* so. (The per-pixel renderer's render pass has the same, from
* upstream.) */
// (the colour attachment is in it as well: once one dependency into the
// subpass is spelled out, the one Vulkan would have assumed for the rest is gone)
dependencies.emplace_back(VK_SUBPASS_EXTERNAL, 0,
vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests
| vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests
| vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::AccessFlagBits::eDepthStencilAttachmentWrite | vk::AccessFlagBits::eColorAttachmentWrite,
vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite
| vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite);
/* The picture is cleared and drawn into an image the frontend was
* handed before, on the queue the two share: whatever read it there -
* a shader, a compute upscaler, a copy - is done before this clears it,
* or a GPU that draws in tiles shows tiles of the next frame with only
* its opaque polygons drawn yet. */
dependencies.emplace_back(VK_SUBPASS_EXTERNAL, 0,
vk::PipelineStageFlagBits::eFragmentShader | vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::AccessFlags(), vk::AccessFlagBits::eColorAttachmentWrite);
// the depth the frame leaves, for the motion vector pass and the upscaler
if (upscaled)
dependencies.emplace_back(0, VK_SUBPASS_EXTERNAL,
vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests,
vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests
| vk::PipelineStageFlagBits::eFragmentShader | vk::PipelineStageFlagBits::eComputeShader,
vk::AccessFlagBits::eDepthStencilAttachmentWrite,
vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eShaderRead);
if (hdr)
{
// the picture drawn, then encoded where it is...
dependencies.emplace_back(0, 1, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eFragmentShader,
vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eInputAttachmentRead, vk::DependencyFlagBits::eByRegion);
// ...into the image the frontend was done with...
dependencies.emplace_back(VK_SUBPASS_EXTERNAL, 1, vk::PipelineStageFlagBits::eFragmentShader | vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::AccessFlags(), vk::AccessFlagBits::eColorAttachmentWrite);
// ...and which it is handed to read
dependencies.emplace_back(1, VK_SUBPASS_EXTERNAL, vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::PipelineStageFlagBits::eFragmentShader | vk::PipelineStageFlagBits::eTransfer,
vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferRead);
}
renderPass = GetContext()->GetDevice().createRenderPassUnique(vk::RenderPassCreateInfo(vk::RenderPassCreateFlags(),
ARRAY_SIZE(attachmentDescriptions) - (hdr ? 0 : 1), attachmentDescriptions,
ARRAY_SIZE(subpasses) - (hdr ? 0 : 1), subpasses,
dependencies.size(), dependencies.data()));
}
if (hdr && !hdrPipeline)
hdrPipeline = GetContext()->HdrSubpassPipeline(*renderPass, samples > 1 ? 2 : 1, samples);
size_t size = VulkanContext::Instance()->GetSwapChainSize();
if (colorAttachments.size() > size)
{
colorAttachments.resize(size);
hdrAttachments.resize(hdr ? size : 0);
framebuffers.resize(size);
if (upscaled)
depthAttachments.resize(size);
}
else
{
// (with SSAA: the picture, then the samples' colour and depth; then the HDR output's image)
vk::ImageView attachments[] = {
nullptr,
samples > 1 ? samplesAttachment->GetImageView() : upscaled ? vk::ImageView() : depthAttachment->GetImageView(),
samples > 1 ? depthAttachment->GetImageView() : nullptr,
nullptr,
};
const u32 count = (samples > 1 ? 3 : 2) + (hdr ? 1 : 0);
while (colorAttachments.size() < size)
{
if (upscaled)
{
// the frame's depth, kept, sampled and copied from (the frontend reads it as it likes)
depthAttachments.push_back(std::unique_ptr(
new FramebufferAttachment(GetContext()->GetPhysicalDevice(), GetContext()->GetDevice())));
depthAttachments.back()->Init(viewport.width, viewport.height, GetContext()->GetDepthFormat(),
vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eSampled
| vk::ImageUsageFlagBits::eTransferSrc, false);
attachments[1] = depthAttachments.back()->GetImageView();
}
colorAttachments.push_back(std::unique_ptr(
new FramebufferAttachment(GetContext()->GetPhysicalDevice(), GetContext()->GetDevice())));
// (copied from when the context goes, to keep the picture: rend/last_picture.h; the HDR output's input)
colorAttachments.back()->Init(viewport.width, viewport.height, GetContext()->GetColorFormat(),
vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferSrc
| (storagePictures ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlags())
| (hdr ? vk::ImageUsageFlagBits::eInputAttachment : vk::ImageUsageFlags()), false);
attachments[0] = colorAttachments.back()->GetImageView();
if (hdr)
{
hdrAttachments.push_back(std::unique_ptr(
new FramebufferAttachment(GetContext()->GetPhysicalDevice(), GetContext()->GetDevice())));
hdrAttachments.back()->Init(viewport.width, viewport.height, GetContext()->GetHdrFormat(),
vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferSrc, false);
attachments[count - 1] = hdrAttachments.back()->GetImageView();
}
vk::FramebufferCreateInfo createInfo(vk::FramebufferCreateFlags(), *renderPass,
count, attachments, viewport.width, viewport.height, 1);
framebuffers.push_back(GetContext()->GetDevice().createFramebufferUnique(createInfo));
}
}
if (!screenPipelineManager)
screenPipelineManager = std::unique_ptr(new PipelineManager());
screenPipelineManager->Init(shaderManager, *renderPass, samples);
Drawer::Init(samplerManager, screenPipelineManager.get());
if (samples > 1)
{
ssaaResolve.Init(*renderPass, 1, samples, samplesAttachment->GetImageView());
msQuadPipeline.Init(shaderManager, *renderPass, 0, samples);
}
if (upscaled)
{
std::vector depthViews;
for (const auto& attachment : depthAttachments)
depthViews.push_back(attachment->GetImageView());
motionPass.Init(viewport, GetContext()->GetDepthFormat(), vk::ImageLayout::eDepthStencilAttachmentOptimal,
depthViews, screenPipelineManager->GetPipelineLayout(),
shaderManager->GetMotionVertexShader(), shaderManager->GetMotionShader(), commandPool);
}
timer.Init(GetContext()->GetSwapChainSize(), "per-triangle renderer");
if (remade)
{
std::vector views;
for (const auto& attachment : colorAttachments)
views.push_back(attachment->GetImageView());
writeOut.Init(storagePictures ? views : std::vector(), viewport);
}
}
vk::CommandBuffer ScreenDrawer::BeginRenderPass()
{
NewImage();
vk::CommandBuffer commandBuffer = commandPool->Allocate();
commandBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit));
matrices.CalcMatrices(&pvrrc);
// the frame drawn under this frame's jitter, its strips matched for the motion vector pass
if (upscaled && motionPass.Begin(GetCurrentImage()))
{
float jx, jy;
upscaler_jitter(&jx, &jy);
matrices.Jitter(jx, jy);
}
vertexUniformBuffer = WriteVertexUniforms(commandBuffer, GetCurrentImage());
// (the HDR output's transform, for the render pass's last subpass)
if (hdrPipeline)
GetContext()->HdrPrepare(*commandPool, commandBuffer);
/* A framebuffer the game has not drawn to lately starts as the border
* colour, which is what the OpenGL renderers clear it to; any other
* starts black, and has the last picture put into it below. */
const std::array clear_color = pvrrc.clearFramebuffer
? std::array { VO_BORDER_COL.Red / 255.f, VO_BORDER_COL.Green / 255.f, VO_BORDER_COL.Blue / 255.f, 1.f }
: std::array { 0.f, 0.f, 0.f, 1.f };
timer.Begin(commandBuffer, commandPool->GetIndex());
// (with SSAA: the picture, all of which the resolve writes, then the samples' colour and depth)
const vk::ClearValue clear_colors[] = { vk::ClearColorValue(clear_color), vk::ClearDepthStencilValue { 0.f, 0 } };
const vk::ClearValue ssaa_clear_colors[] = { clear_colors[0], clear_colors[0], clear_colors[1] };
commandBuffer.beginRenderPass(vk::RenderPassBeginInfo(*renderPass, *framebuffers[GetCurrentImage()],
vk::Rect2D( { 0, 0 }, viewport), samples > 1 ? 3 : 2, samples > 1 ? ssaa_clear_colors : clear_colors),
vk::SubpassContents::eInline);
commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, viewport.width, viewport.height, 0.0f, 1.0f));
SetBaseScissor();
/* A render that covers only part of the screen leaves the rest of the
* framebuffer as it was. Every frame here goes to a fresh image,
* cleared, so the part that is not drawn was black: the last frame is
* drawn into it first, whole, and the render goes over it. Only for
* such a render - one that covers the screen pays nothing - and not
* into a framebuffer the game has not drawn to lately, which starts
* empty. */
if ((havePicture || restoredPicture) && quadPipeline != nullptr && !pvrrc.clearFramebuffer && matrices.IsClipped())
{
const int count = (int)colorAttachments.size();
const std::array opaque = { 1.f, 1.f, 1.f, 1.f };
commandBuffer.setScissor(0, vk::Rect2D( { 0, 0 }, viewport));
commandBuffer.setBlendConstants(opaque.data());
(samples > 1 ? &msQuadPipeline : quadPipeline)->BindPipeline(commandBuffer);
/* (the picture kept from another context can be of another size) */
if (havePicture)
lastPicture.Draw(commandBuffer, colorAttachments[(GetCurrentImage() + count - 1) % count]->GetImageView(), nullptr, true, commandPool);
else
lastPicture.Draw(commandBuffer, restoredPicture, nullptr, false, commandPool);
}
commandBuffer.setScissor(0, baseScissor);
currentCommandBuffer = commandBuffer;
return commandBuffer;
}
void ScreenDrawer::KeepPicture()
{
if (!havePicture)
return;
u8 *pixels = last_picture_keep(viewport.width, viewport.height);
if (pixels != NULL)
vk_read_picture(colorAttachments[GetCurrentImage()]->GetImage(), GetContext()->GetColorFormat(),
viewport.width, viewport.height, pixels);
}
bool ScreenDrawer::WriteOutBeforeOsd()
{
if (!writeOut.Runs(viewport.height))
return false;
/* (the HDR output's subpass passed over: the picture is encoded once it is written out) */
if (hdrPipeline)
currentCommandBuffer.nextSubpass(vk::SubpassContents::eInline);
currentCommandBuffer.endRenderPass();
writeOut.Record(currentCommandBuffer, GetCurrentImage(), colorAttachments[GetCurrentImage()]->GetImage(),
colorAttachments[GetCurrentImage()]->GetImageView(), viewport.width, viewport.height);
writeOut.BeginOverlay(currentCommandBuffer, GetCurrentImage());
overlaid = true;
return true;
}
void ScreenDrawer::EndRenderPass()
{
havePicture = true;
restoredPicture = nullptr;
// (this frame may have drawn it: it goes when the frame is done with)
if (carriedPicture)
commandPool->DeferDelete(std::move(carriedPicture));
/* The HDR output: the picture encoded in the render pass's last
* subpass, where it is; on a frame the chip's write-out changes after
* the render pass, once that is done (and what goes over it drawn), in
* the same submission */
bool hdrAfter = false;
if (hdrPipeline && !overlaid)
{
currentCommandBuffer.nextSubpass(vk::SubpassContents::eInline);
/* (or with the post effects, which only the pass of its own draws) */
hdrAfter = writeOut.Runs(viewport.height) || GetContext()->HdrPostActive();
if (!hdrAfter)
GetContext()->HdrSubpassDraw(*commandPool, currentCommandBuffer, *hdrPipeline,
colorAttachments[GetCurrentImage()]->GetImageView(),
samples > 1 ? samplesAttachment->GetImageView() : vk::ImageView(), viewport);
}
else if (hdrPipeline)
hdrAfter = true;
currentCommandBuffer.endRenderPass();
if (overlaid)
overlaid = false;
else
writeOut.Record(currentCommandBuffer, GetCurrentImage(), colorAttachments[GetCurrentImage()]->GetImage(),
colorAttachments[GetCurrentImage()]->GetImageView(), viewport.width, viewport.height);
if (motionPass.Recorded())
{
// (what went over the picture may have bound its own)
GetCurrentDescSet().BindPerFrameDescriptorSets(currentCommandBuffer);
motionPass.Record(currentCommandBuffer, GetCurrentImage(), geometry, offsets.indexOffset, baseScissor, geometryVertices);
}
timer.End(currentCommandBuffer, commandPool->GetIndex());
currentCommandBuffer.end();
currentCommandBuffer = nullptr;
vk::Image image = colorAttachments[GetCurrentImage()]->GetImage();
vk::ImageView view = colorAttachments[GetCurrentImage()]->GetImageView();
bool hdr = false;
if (hdrAfter)
hdr = GetContext()->HdrEncode(*commandPool, image, view, vk::Extent2D(viewport.width, viewport.height));
else if (hdrPipeline)
{
image = hdrAttachments[GetCurrentImage()]->GetImage();
view = hdrAttachments[GetCurrentImage()]->GetImageView();
hdr = true;
}
commandPool->EndFrame();
GetContext()->PresentFrame(image, view, vk::Offset2D(viewport.width, viewport.height), hdr);
if (motionPass.Recorded())
motionPass.Submit(GetCurrentImage(), depthAttachments[GetCurrentImage()]->GetImage(),
depthAttachments[GetCurrentImage()]->GetImageView(), GetContext()->GetDepthFormat());
}