From ef11900a3925d4cd91713aa44cd3335e44a12621 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Fri, 2 Sep 2022 12:21:39 +0100 Subject: [PATCH] Introduce reworked Maxwell 3D core interconnect This mainly distributes operations down to activeState and pipelineState, aside from clears which are implemented in-place. The exposed interface is much reduced as opposed to the previous GraphicsContext system due to the newly introduced dirty system, this should hopefully make the code more maintainable and keep actual rendering operations seperate from primitive restart state or whatever. Currently draws are unimplemented and the only full implemented things are clears and constant buffer operations. --- .../interconnect/maxwell_3d/maxwell_3d.cpp | 167 ++++++++++++++++++ .../gpu/interconnect/maxwell_3d/maxwell_3d.h | 67 +++++++ 2 files changed, 234 insertions(+) create mode 100644 app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/maxwell_3d.cpp create mode 100644 app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/maxwell_3d.h diff --git a/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/maxwell_3d.cpp b/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/maxwell_3d.cpp new file mode 100644 index 00000000..2cb8ef07 --- /dev/null +++ b/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/maxwell_3d.cpp @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2022 Ryujinx Team and Contributors (https://github.com/Ryujinx/) +// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include +#include +#include "common/utils.h" +#include "maxwell_3d.h" +#include "common.h" +#include "pipeline_state.h" +#include "services/hosbinder/GraphicBufferProducer.h" + +namespace skyline::gpu::interconnect::maxwell3d { + Maxwell3D::Maxwell3D(GPU &gpu, + soc::gm20b::ChannelContext &channelCtx, + gpu::interconnect::CommandExecutor &executor, + DirtyManager &manager, + const EngineRegisterBundle ®isterBundle) + : ctx{channelCtx, executor, gpu}, + activeState{manager, registerBundle.activeStateRegisters}, + clearEngineRegisters{registerBundle.clearRegisters}, + constantBuffers{manager, registerBundle.constantBufferSelectorRegisters}, + directState{activeState.directState} { + executor.AddFlushCallback([this] { + activeState.MarkAllDirty(); + constantBuffers.MarkAllDirty(); + }); + } + + vk::Rect2D Maxwell3D::GetClearScissor() { + const auto &clearSurfaceControl{clearEngineRegisters.clearSurfaceControl}; + + const auto &surfaceClip{clearEngineRegisters.surfaceClip}; + vk::Rect2D scissor{ + {surfaceClip.horizontal.x, surfaceClip.vertical.y}, + {surfaceClip.horizontal.width, surfaceClip.vertical.height} + }; + + auto rectIntersection{[](const vk::Rect2D &a, const vk::Rect2D &b) -> vk::Rect2D { + vk::Offset2D maxOffset{std::max(a.offset.x, b.offset.x), std::max(a.offset.y, b.offset.y)}; + i32 signedWidth{std::min(a.offset.x + static_cast(a.extent.width), b.offset.x + static_cast(b.extent.width)) - maxOffset.x}; + i32 signedHeight{std::min(a.offset.y + static_cast(a.extent.height), b.offset.y + static_cast(b.extent.height)) - maxOffset.y}; + + return { + maxOffset, + {static_cast(std::max(signedWidth, 0)), + static_cast(std::max(signedHeight, 0))} + }; + }}; + + if (clearSurfaceControl.useClearRect) { + const auto &clearRect{clearEngineRegisters.clearRect}; + scissor = rectIntersection(scissor, { + {clearRect.horizontal.xMin, clearRect.vertical.yMin}, + {static_cast(clearRect.horizontal.xMax - clearRect.horizontal.xMin), static_cast(clearRect.vertical.yMax - clearRect.vertical.yMin)} + }); + } + + if (clearSurfaceControl.useScissor0) { + const auto &scissor0{clearEngineRegisters.scissor0}; + scissor = rectIntersection(scissor, { + {scissor0.horizontal.xMin, scissor0.vertical.yMin}, + {static_cast(scissor0.horizontal.xMax - scissor0.horizontal.xMin), static_cast(scissor0.vertical.yMax - scissor0.vertical.yMin)} + }); + } + + if (clearSurfaceControl.useViewportClip0) { + const auto &viewportClip0{clearEngineRegisters.viewportClip0}; + scissor = rectIntersection(scissor, { + {viewportClip0.horizontal.x0, viewportClip0.vertical.y0}, + {viewportClip0.horizontal.width, viewportClip0.vertical.height} + }); + } + + return scissor; + } + + void Maxwell3D::LoadConstantBuffer(span data, u32 offset) { + constantBuffers.Load(ctx, data, offset); + } + + void Maxwell3D::BindConstantBuffer(engine::PipelineStage stage, u32 index, bool enable) { + if (enable) + constantBuffers.Bind(ctx, stage, index); + else + constantBuffers.Unbind(stage, index); + } + + void Maxwell3D::Clear(engine::ClearSurface &clearSurface) { + auto scissor{GetClearScissor()}; + if (scissor.extent.width == 0 || scissor.extent.height == 0) + return; + + auto needsAttachmentClearCmd{[&](auto &view) { + return scissor.offset.x != 0 || scissor.offset.y != 0 || + scissor.extent != vk::Extent2D{view->texture->dimensions} || + view->range.layerCount != 1 || view->range.baseArrayLayer != 0 || clearSurface.rtArrayIndex != 0; + }}; + + auto clearRects{util::MakeFilledArray(vk::ClearRect{.rect = scissor, .baseArrayLayer = clearSurface.rtArrayIndex, .layerCount = 1})}; + boost::container::small_vector clearAttachments; + + std::shared_ptr colorView{}; + std::shared_ptr depthStencilView{}; + + if (clearSurface.rEnable || clearSurface.gEnable || clearSurface.bEnable || clearSurface.aEnable) { + colorView = activeState.GetColorRenderTargetForClear(ctx, clearSurface.mrtSelect); + ctx.executor.AttachTexture(&*colorView); + + if (!(clearSurface.rEnable && clearSurface.gEnable && clearSurface.bEnable && clearSurface.aEnable)) + Logger::Warn("Partial clears are unimplemented! Performing full clear instead"); + + if (!(colorView->range.aspectMask & vk::ImageAspectFlagBits::eColor)) { + Logger::Warn("Colour RT used in clear lacks colour aspect"); // TODO: Drop this check after texman rework + return; + } + + if (needsAttachmentClearCmd(colorView)) { + clearAttachments.push_back({ .aspectMask = colorView->range.aspectMask, .clearValue = {clearEngineRegisters.colorClearValue} }); + } else { + ctx.executor.AddClearColorSubpass(&*colorView, clearEngineRegisters.colorClearValue); + colorView = {}; // Will avoid adding as colour RT + } + } + + if (clearSurface.stencilEnable || clearSurface.zEnable) { + depthStencilView = activeState.GetDepthRenderTargetForClear(ctx); + ctx.executor.AttachTexture(&*depthStencilView); + + bool viewHasDepth{depthStencilView->range.aspectMask & vk::ImageAspectFlagBits::eDepth}, viewHasStencil{depthStencilView->range.aspectMask & vk::ImageAspectFlagBits::eStencil}; + vk::ClearDepthStencilValue clearValue{ + .depth = clearEngineRegisters.depthClearValue, + .stencil = clearEngineRegisters.stencilClearValue + }; + + + if (!viewHasDepth && !viewHasStencil) { + Logger::Warn("Depth stencil RT used in clear lacks depth or stencil aspects"); // TODO: Drop this check after texman rework + return; + } + + if (needsAttachmentClearCmd(depthStencilView) || (!clearSurface.stencilEnable && viewHasStencil) || (!clearSurface.zEnable && viewHasDepth )) { // Subpass clears write to all aspects of the texture, so we can't use them when only one component is enabled + clearAttachments.push_back({ .aspectMask = depthStencilView->range.aspectMask, .clearValue = clearValue }); + } else { + ctx.executor.AddClearDepthStencilSubpass(&*depthStencilView, clearValue); + depthStencilView = {}; // Will avoid adding as depth-stencil RT + } + } + + // Always use surfaceClip for render area since it's more likely to match the renderArea of draws and avoid an RP break + const auto &surfaceClip{clearEngineRegisters.surfaceClip}; + vk::Rect2D renderArea{{surfaceClip.horizontal.x, surfaceClip.vertical.y}, {surfaceClip.horizontal.width, surfaceClip.vertical.height}}; + + std::array colorAttachments{colorView ? &*colorView : nullptr}; + ctx.executor.AddSubpass([clearAttachments, clearRects](vk::raii::CommandBuffer &commandBuffer, const std::shared_ptr &, GPU &, vk::RenderPass, u32) { + commandBuffer.clearAttachments(clearAttachments, span(clearRects).first(clearAttachments.size())); + }, renderArea, {}, colorView ? colorAttachments : span{}, depthStencilView ? &*depthStencilView : nullptr); + } + + void Maxwell3D::Draw(engine::DrawTopology topology, bool indexed, u32 count, u32 first, u32 instanceCount, u32 vertexOffset, u32 firstInstance) { + StateUpdateBuilder builder{}; + + + + activeState.Update(ctx, builder, indexed, topology, count); + } +} \ No newline at end of file diff --git a/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/maxwell_3d.h b/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/maxwell_3d.h new file mode 100644 index 00000000..7230adf8 --- /dev/null +++ b/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/maxwell_3d.h @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include "common.h" +#include "active_state.h" +#include "constant_buffers.h" + +namespace skyline::gpu::interconnect::maxwell3d { + /** + * @brief The core Maxwell 3D interconnect object, directly accessed by the engine code to perform rendering operations + */ + class Maxwell3D { + public: + struct ClearEngineRegisters { + const engine::Scissor &scissor0; + const engine::ViewportClip &viewportClip0; + const engine::ClearRect &clearRect; + const std::array &colorClearValue; + const float &depthClearValue; + const u32 &stencilClearValue; + const engine::SurfaceClip &surfaceClip; + const engine::ClearSurfaceControl &clearSurfaceControl; + }; + + /** + * @brief The full set of register state used by the GPU interconnect + */ + struct EngineRegisterBundle { + ActiveState::EngineRegisters activeStateRegisters; + ClearEngineRegisters clearRegisters; + ConstantBufferSelectorState::EngineRegisters constantBufferSelectorRegisters; + }; + + private: + InterconnectContext ctx; + ActiveState activeState; + ClearEngineRegisters clearEngineRegisters; + ConstantBuffers constantBuffers; + + vk::Rect2D GetClearScissor(); + + public: + DirectPipelineState &directState; + + Maxwell3D(GPU &gpu, + soc::gm20b::ChannelContext &channelCtx, + gpu::interconnect::CommandExecutor &executor, + DirtyManager &manager, + const EngineRegisterBundle ®isterBundle); + + /** + * @brief Loads the given data into the constant buffer pointed by the constant buffer selector starting at the given offset + */ + void LoadConstantBuffer(span data, u32 offset); + + /** + * @brief Binds the constant buffer selector to the given pipeline stage + */ + void BindConstantBuffer(engine::PipelineStage stage, u32 index, bool enable); + + void Clear(engine::ClearSurface &clearSurface); + + void Draw(engine::DrawTopology topology, bool indexed, u32 count, u32 first, u32 instanceCount, u32 vertexOffset, u32 firstInstance); + }; +}