diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index d38658d6a9..e7816e7d06 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -30,6 +30,7 @@ add_executable(tests core/priority_queue.cpp core/internal_network/network.cpp video_core/frame_dump_schedule.cpp + video_core/framebuffer_limits.cpp video_core/memory_tracker.cpp video_core/spirv_storage_alignment.cpp video_core/pipeline_optimization.cpp diff --git a/src/tests/video_core/framebuffer_limits.cpp b/src/tests/video_core/framebuffer_limits.cpp new file mode 100644 index 0000000000..e92c9fddb7 --- /dev/null +++ b/src/tests/video_core/framebuffer_limits.cpp @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "video_core/renderer_vulkan/framebuffer_limits.h" + +namespace { +constexpr Vulkan::FramebufferSize APPLE_LIMITS{16384, 16384, 2048}; +} // Anonymous namespace + +TEST_CASE("Attachment-less framebuffers stay within the device limits", + "[video_core][framebuffer_limits]") { + const auto size = Vulkan::ClampFramebufferSize({32768, 32768, 1}, APPLE_LIMITS); + REQUIRE(size.width == 16384); + REQUIRE(size.height == 16384); + REQUIRE(size.layers == 1); +} + +TEST_CASE("Framebuffer sizes within the device limits are unchanged", + "[video_core][framebuffer_limits]") { + const auto size = Vulkan::ClampFramebufferSize({1920, 1080, 6}, APPLE_LIMITS); + REQUIRE(size.width == 1920); + REQUIRE(size.height == 1080); + REQUIRE(size.layers == 6); +} + +TEST_CASE("Framebuffer layers are clamped to the device limit", + "[video_core][framebuffer_limits]") { + const auto size = Vulkan::ClampFramebufferSize({64, 64, 4096}, APPLE_LIMITS); + REQUIRE(size.layers == 2048); +} + +TEST_CASE("Framebuffers keep at least one pixel and one layer", + "[video_core][framebuffer_limits]") { + const auto size = Vulkan::ClampFramebufferSize({0, 0, 0}, APPLE_LIMITS); + REQUIRE(size.width == 1); + REQUIRE(size.height == 1); + REQUIRE(size.layers == 1); +} diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index c0648802ee..136dcc6f2e 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -159,6 +159,7 @@ add_library(video_core STATIC renderer_vulkan/blit_image.h renderer_vulkan/fixed_pipeline_state.cpp renderer_vulkan/fixed_pipeline_state.h + renderer_vulkan/framebuffer_limits.h renderer_vulkan/maxwell_to_vk.cpp renderer_vulkan/maxwell_to_vk.h renderer_vulkan/pipeline_helper.h diff --git a/src/video_core/renderer_vulkan/framebuffer_limits.h b/src/video_core/renderer_vulkan/framebuffer_limits.h new file mode 100644 index 0000000000..d11cf19868 --- /dev/null +++ b/src/video_core/renderer_vulkan/framebuffer_limits.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "common/common_types.h" + +namespace Vulkan { + +struct FramebufferSize { + u32 width; + u32 height; + u32 layers; +}; + +// Draws without attachments take their size from the surface clip, which games leave at +// 32768x32768. Apple GPUs allow 16384, and Metal loses the device on a larger framebuffer. +constexpr FramebufferSize ClampFramebufferSize(FramebufferSize size, FramebufferSize max) { + return { + (std::max)(1U, (std::min)(size.width, max.width)), + (std::max)(1U, (std::min)(size.height, max.height)), + (std::max)(1U, (std::min)(size.layers, max.layers)), + }; +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp index 98787b9add..f2bb0ee10b 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp @@ -40,6 +40,12 @@ constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 128_MiB; constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 128_MiB; #endif +// Unused space after the end of the stream buffer. Constant buffers are bound from it and the +// shaders declare each one at its full 64 KiB, so Metal checks 64 KiB from the binding offset +// against the end of the buffer ("argument c3[0] ... has space for 26624 bytes, but argument has a +// length(65536)"). The tail is never handed out, so every binding has the full 64 KiB behind it. +constexpr VkDeviceSize STREAM_BUFFER_TAIL = 64_KiB; + size_t GetStreamBufferSize(const Device& device) { if (!device.HasDebuggingToolAttached()) { if (device.IsIntegrated()) { @@ -88,7 +94,7 @@ StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& mem .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .pNext = nullptr, .flags = 0, - .size = stream_buffer_size, + .size = stream_buffer_size + STREAM_BUFFER_TAIL, .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 97126571ab..d2e9780273 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -28,6 +28,7 @@ #include "video_core/engines/fermi_2d.h" #include "video_core/renderer_vulkan/blit_image.h" +#include "video_core/renderer_vulkan/framebuffer_limits.h" #include "video_core/renderer_vulkan/maxwell_to_vk.h" #include "video_core/renderer_vulkan/vk_compute_pass.h" #include "video_core/renderer_vulkan/vk_render_pass_cache.h" @@ -4501,6 +4502,13 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime, runtime_ptr = &runtime; render_area.width = (std::min)(render_area.width, width); render_area.height = (std::min)(render_area.height, height); + const FramebufferSize size = ClampFramebufferSize( + {render_area.width, render_area.height, + static_cast((std::max)(num_layers, 1))}, + {runtime.device.GetMaxFramebufferWidth(), runtime.device.GetMaxFramebufferHeight(), + runtime.device.GetMaxFramebufferLayers()}); + render_area = VkExtent2D{.width = size.width, .height = size.height}; + num_layers = static_cast(size.layers); if (do_resolve_color) { const u32 layers = static_cast((std::max)(num_layers, 1)); diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 9cbe7e0339..ff9cb18ba0 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -420,7 +420,10 @@ class Device { FN_MAX_LIMIT_ELEM(DescriptorSetStorageBuffersDynamic) \ FN_MAX_LIMIT_ELEM(DescriptorSetSampledImages) \ FN_MAX_LIMIT_ELEM(DescriptorSetStorageImages) \ - FN_MAX_LIMIT_ELEM(DescriptorSetInputAttachments) + FN_MAX_LIMIT_ELEM(DescriptorSetInputAttachments) \ + FN_MAX_LIMIT_ELEM(FramebufferWidth) \ + FN_MAX_LIMIT_ELEM(FramebufferHeight) \ + FN_MAX_LIMIT_ELEM(FramebufferLayers) #define FN_MAX_LIMIT_ELEM(name) \ u32 GetMax##name() const { return properties.properties.limits.max##name; } FN_MAX_LIMIT_LIST