diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 5a1faf801..6048045b1 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -450,12 +450,21 @@ void RasterizerVulkan::DrawTriangles() { return; } + const u64 vertex_size = vertex_batch.size() * sizeof(HardwareVertex); pipeline_info.rasterization.topology.Assign(Pica::PipelineRegs::TriangleTopology::List); pipeline_info.vertex_layout = software_layout; pipeline_cache.UseTrivialVertexShader(); pipeline_cache.UseTrivialGeometryShader(); + auto [buffer, offset, _] = stream_buffer.Map(vertex_size, sizeof(HardwareVertex)); + std::memcpy(buffer, vertex_batch.data(), vertex_size); + stream_buffer.Commit(vertex_size); + + scheduler.Record([this, offset = offset](vk::CommandBuffer cmdbuf) { + cmdbuf.bindVertexBuffers(0, stream_buffer.Handle(), offset); + }); + Draw(false, false); } @@ -537,45 +546,79 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) { uniform_block_data.dirty = true; } - const auto BindCubeFace = [&](Pica::TexturingRegs::CubeFace face, - Pica::Texture::TextureInfo& info) { - info.physical_address = regs.texturing.GetCubePhysicalAddress(face); - auto surface = res_cache.GetTextureSurface(info); - - const u32 binding = static_cast(face); - if (surface) { - pipeline_cache.BindStorageImage(binding, surface->ImageView()); - } else { - pipeline_cache.BindStorageImage(binding, null_storage_surface.ImageView()); - } - }; - - const auto BindSampler = [&](u32 binding, SamplerInfo& info, - const Pica::TexturingRegs::TextureConfig& config) { - // TODO(GPUCode): Cubemaps don't contain any mipmaps for now, so sampling from them returns - // nothing. Always sample from the base level until mipmaps for texture cubes are - // implemented - const bool skip_mipmap = config.type == Pica::TexturingRegs::TextureConfig::TextureCube; - info = SamplerInfo{.mag_filter = config.mag_filter, - .min_filter = config.min_filter, - .mip_filter = config.mip_filter, - .wrap_s = config.wrap_s, - .wrap_t = config.wrap_t, - .border_color = config.border_color.raw, - .lod_min = skip_mipmap ? 0.f : static_cast(config.lod.min_level), - .lod_max = skip_mipmap ? 0.f : static_cast(config.lod.max_level)}; - - // Search the cache and bind the appropriate sampler - if (auto it = samplers.find(info); it != samplers.end()) { - pipeline_cache.BindSampler(binding, it->second); - } else { - vk::Sampler texture_sampler = CreateSampler(info); - samplers.emplace(info, texture_sampler); - pipeline_cache.BindSampler(binding, texture_sampler); - } - }; - // Sync and bind the texture surfaces + // NOTE: From here onwards its a safe zone to set the draw state, doing that any earlier will + // cause issues as the rasterizer cache might cause a scheduler switch and invalidate our state + SyncTextureUnits(color_surface.get()); + + const vk::Rect2D render_area = { + .offset{static_cast(draw_rect.left), static_cast(draw_rect.bottom)}, + .extent{draw_rect.GetWidth(), draw_rect.GetHeight()}, + }; + renderpass_cache.EnterRenderpass(color_surface.get(), depth_surface.get(), render_area); + + // Sync and bind the shader + if (shader_dirty) { + pipeline_cache.UseFragmentShader(regs); + shader_dirty = false; + } + + // Sync the LUTs within the texture buffer + SyncAndUploadLUTs(); + SyncAndUploadLUTsLF(); + + // Sync the uniform data + UploadUniforms(accelerate); + + // Sync the viewport + pipeline_cache.SetViewport(surfaces_rect.left + viewport_rect_unscaled.left * res_scale, + surfaces_rect.bottom + viewport_rect_unscaled.bottom * res_scale, + viewport_rect_unscaled.GetWidth() * res_scale, + viewport_rect_unscaled.GetHeight() * res_scale); + + // Viewport can have negative offsets or larger dimensions than our framebuffer sub-rect. + // Enable scissor test to prevent drawing outside of the framebuffer region + pipeline_cache.SetScissor(draw_rect.left, draw_rect.bottom, draw_rect.GetWidth(), + draw_rect.GetHeight()); + + // Draw the vertex batch + bool succeeded = true; + if (accelerate) { + succeeded = AccelerateDrawBatchInternal(is_indexed); + } else { + pipeline_cache.BindPipeline(pipeline_info, true); + scheduler.Record([vertex_count = vertex_batch.size()](vk::CommandBuffer cmdbuf) { + cmdbuf.draw(vertex_count, 1, 0, 0); + }); + } + + vertex_batch.clear(); + + // Mark framebuffer surfaces as dirty + const Common::Rectangle draw_rect_unscaled{draw_rect / res_scale}; + if (color_surface && write_color_fb) { + auto interval = color_surface->GetSubRectInterval(draw_rect_unscaled); + res_cache.InvalidateRegion(boost::icl::first(interval), boost::icl::length(interval), + color_surface); + } + + if (depth_surface && write_depth_fb) { + auto interval = depth_surface->GetSubRectInterval(draw_rect_unscaled); + res_cache.InvalidateRegion(boost::icl::first(interval), boost::icl::length(interval), + depth_surface); + } + + static int counter = 20; + counter--; + if (counter == 0) { + scheduler.DispatchWork(); + counter = 20; + } + + return succeeded; +} + +void RasterizerVulkan::SyncTextureUnits(Surface* const color_surface) { const auto pica_textures = regs.texturing.GetTextures(); for (u32 texture_index = 0; texture_index < pica_textures.size(); ++texture_index) { const auto& texture = pica_textures[texture_index]; @@ -597,12 +640,20 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) { using CubeFace = Pica::TexturingRegs::CubeFace; auto info = Pica::Texture::TextureInfo::FromPicaRegister(texture.config, texture.format); - BindCubeFace(CubeFace::PositiveX, info); - BindCubeFace(CubeFace::NegativeX, info); - BindCubeFace(CubeFace::PositiveY, info); - BindCubeFace(CubeFace::NegativeY, info); - BindCubeFace(CubeFace::PositiveZ, info); - BindCubeFace(CubeFace::NegativeZ, info); + for (CubeFace face : + {CubeFace::PositiveX, CubeFace::NegativeX, CubeFace::PositiveY, + CubeFace::NegativeY, CubeFace::PositiveZ, CubeFace::NegativeZ}) { + info.physical_address = regs.texturing.GetCubePhysicalAddress(face); + auto surface = res_cache.GetTextureSurface(info); + + const u32 binding = static_cast(face); + if (surface) { + pipeline_cache.BindStorageImage(binding, surface->ImageView()); + } else { + pipeline_cache.BindStorageImage(binding, + null_storage_surface.ImageView()); + } + } continue; } case TextureType::TextureCube: { @@ -669,96 +720,6 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) { pipeline_cache.BindSampler(texture_index, default_sampler); } } - - // NOTE: From here onwards its a safe zone to set the draw state, doing that any earlier will - // cause issues as the rasterizer cache might cause a scheduler switch and invalidate our state - const vk::Rect2D render_area = { - .offset{ - .x = static_cast(draw_rect.left), - .y = static_cast(draw_rect.bottom), - }, - .extent{ - .width = draw_rect.GetWidth(), - .height = draw_rect.GetHeight(), - }, - }; - - renderpass_cache.EnterRenderpass(color_surface.get(), depth_surface.get(), render_area); - - // Sync and bind the shader - if (shader_dirty) { - pipeline_cache.UseFragmentShader(regs); - shader_dirty = false; - } - - // Sync the LUTs within the texture buffer - SyncAndUploadLUTs(); - SyncAndUploadLUTsLF(); - - // Sync the uniform data - UploadUniforms(accelerate); - - // Sync the viewport - pipeline_cache.SetViewport(surfaces_rect.left + viewport_rect_unscaled.left * res_scale, - surfaces_rect.bottom + viewport_rect_unscaled.bottom * res_scale, - viewport_rect_unscaled.GetWidth() * res_scale, - viewport_rect_unscaled.GetHeight() * res_scale); - - // Viewport can have negative offsets or larger dimensions than our framebuffer sub-rect. - // Enable scissor test to prevent drawing outside of the framebuffer region - pipeline_cache.SetScissor(draw_rect.left, draw_rect.bottom, draw_rect.GetWidth(), - draw_rect.GetHeight()); - - // Draw the vertex batch - bool succeeded = true; - if (accelerate) { - succeeded = AccelerateDrawBatchInternal(is_indexed); - } else { - pipeline_cache.BindPipeline(pipeline_info, true); - - const u32 max_vertices = STREAM_BUFFER_SIZE / sizeof(HardwareVertex); - const u32 batch_size = static_cast(vertex_batch.size()); - for (u32 base_vertex = 0; base_vertex < batch_size; base_vertex += max_vertices) { - const u32 vertices = std::min(max_vertices, batch_size - base_vertex); - const u32 vertex_size = vertices * sizeof(HardwareVertex); - - // Copy vertex data - auto [array_ptr, offset, _] = stream_buffer.Map(vertex_size, sizeof(HardwareVertex)); - std::memcpy(array_ptr, vertex_batch.data() + base_vertex, vertex_size); - stream_buffer.Commit(vertex_size); - - scheduler.Record( - [this, vertices, base_vertex, offset = offset](vk::CommandBuffer cmdbuf) { - cmdbuf.bindVertexBuffers(0, stream_buffer.Handle(), offset); - cmdbuf.draw(vertices, 1, base_vertex, 0); - }); - } - } - - vertex_batch.clear(); - - // Mark framebuffer surfaces as dirty - const Common::Rectangle draw_rect_unscaled{draw_rect / res_scale}; - if (color_surface && write_color_fb) { - auto interval = color_surface->GetSubRectInterval(draw_rect_unscaled); - res_cache.InvalidateRegion(boost::icl::first(interval), boost::icl::length(interval), - color_surface); - } - - if (depth_surface && write_depth_fb) { - auto interval = depth_surface->GetSubRectInterval(draw_rect_unscaled); - res_cache.InvalidateRegion(boost::icl::first(interval), boost::icl::length(interval), - depth_surface); - } - - static int counter = 20; - counter--; - if (counter == 0) { - scheduler.DispatchWork(); - counter = 20; - } - - return succeeded; } void RasterizerVulkan::NotifyFixedFunctionPicaRegisterChanged(u32 id) { @@ -1045,6 +1006,30 @@ void RasterizerVulkan::MakeSoftwareVertexLayout() { } } +void RasterizerVulkan::BindSampler(u32 unit, SamplerInfo& info, + const Pica::TexturingRegs::TextureConfig& config) { + // TODO: Cubemaps don't contain any mipmaps for now, so sampling from them returns + // nothing. Always sample from the base level until mipmaps for texture cubes are + // implemented + const bool skip_mipmap = config.type == Pica::TexturingRegs::TextureConfig::TextureCube; + info = SamplerInfo{ + .mag_filter = config.mag_filter, + .min_filter = config.min_filter, + .mip_filter = config.mip_filter, + .wrap_s = config.wrap_s, + .wrap_t = config.wrap_t, + .border_color = config.border_color.raw, + .lod_min = skip_mipmap ? 0.f : static_cast(config.lod.min_level), + .lod_max = skip_mipmap ? 0.f : static_cast(config.lod.max_level), + }; + + auto [it, new_sampler] = samplers.try_emplace(info); + if (new_sampler) { + it->second = CreateSampler(info); + } + pipeline_cache.BindSampler(unit, it->second); +} + vk::Sampler RasterizerVulkan::CreateSampler(const SamplerInfo& info) { const bool use_border_color = instance.IsCustomBorderColorSupported() && (info.wrap_s == SamplerInfo::TextureConfig::ClampToBorder || diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index b0e3b54f4..5318296a2 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -122,6 +122,9 @@ private: void SyncAndUploadLUTs(); void SyncAndUploadLUTsLF(); + /// Syncs all enabled PICA texture units + void SyncTextureUnits(Surface* const color_surface); + /// Upload the uniform blocks to the uniform buffer object void UploadUniforms(bool accelerate_draw); @@ -149,6 +152,9 @@ private: /// Creates the vertex layout struct used for software shader pipelines void MakeSoftwareVertexLayout(); + /// Binds a sampler to the specified texture unit + void BindSampler(u32 unit, SamplerInfo& info, const Pica::TexturingRegs::TextureConfig& config); + /// Creates a new sampler object vk::Sampler CreateSampler(const SamplerInfo& info); diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index 788a8af23..35e84964f 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -11,6 +11,7 @@ #include "common/alignment.h" #include "common/common_funcs.h" #include "common/common_types.h" +#include "common/logging/log.h" #include "common/polyfill_thread.h" #include "video_core/renderer_vulkan/vk_master_semaphore.h" #include "video_core/renderer_vulkan/vk_resource_pool.h" @@ -98,6 +99,7 @@ public: void Wait(u64 tick) { if (tick >= master_semaphore.CurrentTick()) { // Make sure we are not waiting for the current tick without signalling + LOG_WARNING(Render_Vulkan, "Flushing current tick"); Flush(); } master_semaphore.Wait(tick); diff --git a/src/video_core/renderer_vulkan/vk_texture_runtime.cpp b/src/video_core/renderer_vulkan/vk_texture_runtime.cpp index d84406272..b04090071 100644 --- a/src/video_core/renderer_vulkan/vk_texture_runtime.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_runtime.cpp @@ -19,6 +19,8 @@ MICROPROFILE_DEFINE(Vulkan_Download, "Vulkan", "Texture Download", MP_RGB(128, 1 namespace Vulkan { +using VideoCore::PixelFormatAsString; + struct RecordParams { vk::ImageAspectFlags aspect; vk::Filter filter; @@ -127,10 +129,10 @@ TextureRuntime::~TextureRuntime() { for (const auto& [key, alloc] : texture_recycler) { vmaDestroyImage(allocator, alloc.image, alloc.allocation); - device.destroyImageView(alloc.image_view); - if (alloc.base_view) { + if (alloc.base_view && alloc.base_view != alloc.image_view) { device.destroyImageView(alloc.base_view); } + device.destroyImageView(alloc.image_view); if (alloc.depth_view) { device.destroyImageView(alloc.depth_view); device.destroyImageView(alloc.stencil_view); @@ -264,59 +266,8 @@ ImageAlloc TextureRuntime::Allocate(u32 width, u32 height, VideoCore::PixelForma vk::Device device = instance.GetDevice(); alloc.image_view = device.createImageView(view_info); - - // Also create a base mip view in case this is used as an attachment - if (levels > 1) [[likely]] { - const vk::ImageViewCreateInfo base_view_info = { - .image = alloc.image, - .viewType = view_type, - .format = format, - .subresourceRange{ - .aspectMask = alloc.aspect, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = layers, - }, - }; - - alloc.base_view = device.createImageView(base_view_info); - } - - // Create seperate depth/stencil views in case this gets reinterpreted with a compute shader - if (alloc.aspect & vk::ImageAspectFlagBits::eStencil) { - vk::ImageViewCreateInfo view_info = { - .image = alloc.image, - .viewType = view_type, - .format = format, - .subresourceRange{ - .aspectMask = vk::ImageAspectFlagBits::eDepth, - .baseMipLevel = 0, - .levelCount = levels, - .baseArrayLayer = 0, - .layerCount = layers, - }, - }; - - alloc.depth_view = device.createImageView(view_info); - view_info.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eStencil; - alloc.stencil_view = device.createImageView(view_info); - } - - if (create_storage_view) { - const vk::ImageViewCreateInfo storage_view_info = { - .image = alloc.image, - .viewType = view_type, - .format = vk::Format::eR32Uint, - .subresourceRange{ - .aspectMask = alloc.aspect, - .baseMipLevel = 0, - .levelCount = levels, - .baseArrayLayer = 0, - .layerCount = layers, - }, - }; - alloc.storage_view = device.createImageView(storage_view_info); + if (levels == 1) { + alloc.base_view = alloc.image_view; } renderpass_cache.ExitRenderpass(); @@ -1051,6 +1002,100 @@ vk::PipelineStageFlags Surface::PipelineStageFlags() const noexcept { : vk::PipelineStageFlagBits::eNone); } +vk::ImageView Surface::FramebufferView() noexcept { + vk::ImageView& base_view = alloc.base_view; + if (base_view) { + return base_view; + } + + const vk::ImageViewCreateInfo base_view_info = { + .image = alloc.image, + .viewType = vk::ImageViewType::e2D, + .format = instance.GetTraits(pixel_format).native, + .subresourceRange{ + .aspectMask = alloc.aspect, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + base_view = instance.GetDevice().createImageView(base_view_info); + return base_view; +} + +vk::ImageView Surface::DepthView() noexcept { + vk::ImageView& depth_view = alloc.depth_view; + if (depth_view) { + return depth_view; + } + + const vk::ImageViewCreateInfo view_info = { + .image = alloc.image, + .viewType = vk::ImageViewType::e2D, + .format = instance.GetTraits(pixel_format).native, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eDepth, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + + depth_view = instance.GetDevice().createImageView(view_info); + return depth_view; +} + +vk::ImageView Surface::StencilView() noexcept { + vk::ImageView& stencil_view = alloc.stencil_view; + if (stencil_view) { + return stencil_view; + } + + const vk::ImageViewCreateInfo view_info = { + .image = alloc.image, + .viewType = vk::ImageViewType::e2D, + .format = instance.GetTraits(pixel_format).native, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eStencil, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + + stencil_view = instance.GetDevice().createImageView(view_info); + return stencil_view; +} + +vk::ImageView Surface::StorageView() noexcept { + vk::ImageView& storage_view = alloc.storage_view; + if (storage_view) { + return storage_view; + } + + ASSERT_MSG(pixel_format == VideoCore::PixelFormat::RGBA8, + "Attempted to retrieve storage view from unsupported surface with format {}", + PixelFormatAsString(pixel_format)); + + const vk::ImageViewCreateInfo storage_view_info = { + .image = alloc.image, + .viewType = vk::ImageViewType::e2D, + .format = vk::Format::eR32Uint, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + storage_view = instance.GetDevice().createImageView(storage_view_info); + return storage_view; +} + void Surface::ScaledUpload(const VideoCore::BufferTextureCopy& upload, const StagingData& staging) { const u32 rect_width = upload.texture_rect.GetWidth(); const u32 rect_height = upload.texture_rect.GetHeight(); diff --git a/src/video_core/renderer_vulkan/vk_texture_runtime.h b/src/video_core/renderer_vulkan/vk_texture_runtime.h index e34710785..ac7bfaf3d 100644 --- a/src/video_core/renderer_vulkan/vk_texture_runtime.h +++ b/src/video_core/renderer_vulkan/vk_texture_runtime.h @@ -170,21 +170,6 @@ public: vk::ImageAspectFlags aspect, TextureRuntime& runtime); ~Surface() override; - /// Uploads pixel data in staging to a rectangle region of the surface texture - void Upload(const VideoCore::BufferTextureCopy& upload, const StagingData& staging); - - /// Downloads pixel data to staging from a rectangle region of the surface texture - void Download(const VideoCore::BufferTextureCopy& download, const StagingData& staging); - - /// Returns the bpp of the internal surface format - u32 GetInternalBytesPerPixel() const; - - /// Returns the access flags indicative of the surface - vk::AccessFlags AccessFlags() const noexcept; - - /// Returns the pipeline stage flags indicative of the surface - vk::PipelineStageFlags PipelineStageFlags() const noexcept; - /// Returns the surface aspect vk::ImageAspectFlags Aspect() const noexcept { return alloc.aspect; @@ -200,34 +185,32 @@ public: return alloc.image_view; } + /// Uploads pixel data in staging to a rectangle region of the surface texture + void Upload(const VideoCore::BufferTextureCopy& upload, const StagingData& staging); + + /// Downloads pixel data to staging from a rectangle region of the surface texture + void Download(const VideoCore::BufferTextureCopy& download, const StagingData& staging); + + /// Returns the bpp of the internal surface format + u32 GetInternalBytesPerPixel() const; + + /// Returns the access flags indicative of the surface + vk::AccessFlags AccessFlags() const noexcept; + + /// Returns the pipeline stage flags indicative of the surface + vk::PipelineStageFlags PipelineStageFlags() const noexcept; + /// Returns an image view used to create a framebuffer - vk::ImageView FramebufferView() noexcept { - is_framebuffer = true; - return alloc.base_view; - } + vk::ImageView FramebufferView() noexcept; - /// Returns the depth only image view of the surface, null otherwise - vk::ImageView DepthView() const noexcept { - return alloc.depth_view; - } + /// Returns the depth only image view of the surface + vk::ImageView DepthView() noexcept; - /// Returns the stencil only image view of the surface, null otherwise - vk::ImageView StencilView() const noexcept { - return alloc.stencil_view; - } + /// Returns the stencil only image view of the surface + vk::ImageView StencilView() noexcept; /// Returns the R32 image view used for atomic load/store - vk::ImageView StorageView() noexcept { - if (!alloc.storage_view) { - LOG_CRITICAL(Render_Vulkan, - "Surface with pixel format {} and internal format {} " - "does not provide requested storage view!", - VideoCore::PixelFormatAsString(pixel_format), vk::to_string(alloc.format)); - UNREACHABLE(); - } - is_storage = true; - return alloc.storage_view; - } + vk::ImageView StorageView() noexcept; private: /// Uploads pixel data to scaled texture