fix(vk): Make the color ramp pass interruptible (#11270) ba953a142b

Color ramps are the final resource texture we need to make interruptible
for old Android GPUs that don't support complex render passes.

Also fix lots_of_tess_spans to look the same on MSAA and not.

Co-authored-by: Chris Dalton <99840794+csmartdalton@users.noreply.github.com>
This commit is contained in:
csmartdalton
2025-12-16 21:52:24 +00:00
parent c52a714032
commit 0a978321ee
4 changed files with 111 additions and 112 deletions

View File

@@ -1 +1 @@
bc6f965d1f8c5e298073dc65b6298806baaaa9b9
ba953a142ba3703152a81b89da93ea77ecb30d57

View File

@@ -280,10 +280,17 @@ private:
// Renders color ramps to the gradient texture.
class RenderContextVulkanImpl::ColorRampPipeline
: public ResourceTexturePipeline
{
public:
ColorRampPipeline(PipelineManagerVulkan* pipelineManager) :
m_vk(ref_rcp(pipelineManager->vulkanContext()))
ColorRampPipeline(PipelineManagerVulkan* pipelineManager,
const DriverWorkarounds& workarounds) :
ResourceTexturePipeline(ref_rcp(pipelineManager->vulkanContext()),
VK_FORMAT_R8G8B8A8_UNORM,
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
"ColorRamp",
workarounds)
{
VkDescriptorSetLayout perFlushDescriptorSetLayout =
pipelineManager->perFlushDescriptorSetLayout();
@@ -359,53 +366,6 @@ public:
.pVertexAttributeDescriptions = &vertexAttributeDescription,
};
VkAttachmentDescription attachment = {
.format = VK_FORMAT_R8G8B8A8_UNORM,
.samples = VK_SAMPLE_COUNT_1_BIT,
.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
};
VkSubpassDependency dependencies[] = {
// Wait for previous accesses to complete before this renderpass
// starts
{
.srcSubpass = VK_SUBPASS_EXTERNAL,
.dstSubpass = 0,
.srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
.srcAccessMask = VK_ACCESS_SHADER_READ_BIT,
.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
},
{
.srcSubpass = 0,
.dstSubpass = VK_SUBPASS_EXTERNAL,
.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
.dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
},
};
VkRenderPassCreateInfo renderPassCreateInfo = {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
.attachmentCount = 1,
.pAttachments = &attachment,
.subpassCount = 1,
.pSubpasses = &layout::SINGLE_ATTACHMENT_SUBPASS,
.dependencyCount = std::size(dependencies),
.pDependencies = dependencies,
};
VK_CHECK(m_vk->CreateRenderPass(m_vk->device,
&renderPassCreateInfo,
nullptr,
&m_renderPass));
m_vk->setDebugNameIfEnabled(uint64_t(m_renderPass),
VK_OBJECT_TYPE_RENDER_PASS,
"Color Ramp RenderPass");
VkGraphicsPipelineCreateInfo pipelineCreateInfo = {
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.stageCount = 2,
@@ -418,7 +378,7 @@ public:
.pColorBlendState = &layout::SINGLE_ATTACHMENT_BLEND_DISABLED,
.pDynamicState = &layout::DYNAMIC_VIEWPORT_SCISSOR,
.layout = m_pipelineLayout,
.renderPass = m_renderPass,
.renderPass = renderPass(),
};
VK_CHECK(m_vk->CreateGraphicsPipelines(m_vk->device,
@@ -438,18 +398,14 @@ public:
~ColorRampPipeline()
{
m_vk->DestroyPipelineLayout(m_vk->device, m_pipelineLayout, nullptr);
m_vk->DestroyRenderPass(m_vk->device, m_renderPass, nullptr);
m_vk->DestroyPipeline(m_vk->device, m_renderPipeline, nullptr);
}
VkPipelineLayout pipelineLayout() const { return m_pipelineLayout; }
VkRenderPass renderPass() const { return m_renderPass; }
VkPipeline renderPipeline() const { return m_renderPipeline; }
private:
rcp<VulkanContext> m_vk;
VkPipelineLayout m_pipelineLayout;
VkRenderPass m_renderPass;
VkPipeline m_renderPipeline;
};
@@ -936,7 +892,8 @@ void RenderContextVulkanImpl::initGPUObjects(
// The pipelines reference our vulkan objects. Delete them first.
m_colorRampPipeline =
std::make_unique<ColorRampPipeline>(m_pipelineManager.get());
std::make_unique<ColorRampPipeline>(m_pipelineManager.get(),
m_workarounds);
m_tessellatePipeline =
std::make_unique<TessellatePipeline>(m_pipelineManager.get(),
m_workarounds);
@@ -1910,20 +1867,9 @@ void RenderContextVulkanImpl::flush(const FlushDescriptor& desc)
.extent = {gpu::kGradTextureWidth, desc.gradDataHeight},
};
VkRenderPassBeginInfo renderPassBeginInfo = {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
.renderPass = m_colorRampPipeline->renderPass(),
.framebuffer = *m_gradTextureFramebuffer,
.renderArea = renderArea,
};
m_vk->CmdBeginRenderPass(commandBuffer,
&renderPassBeginInfo,
VK_SUBPASS_CONTENTS_INLINE);
m_vk->CmdBindPipeline(commandBuffer,
VK_PIPELINE_BIND_POINT_GRAPHICS,
m_colorRampPipeline->renderPipeline());
m_colorRampPipeline->beginRenderPass(commandBuffer,
renderArea,
*m_gradTextureFramebuffer);
m_vk->CmdSetViewport(commandBuffer,
0,
@@ -1950,11 +1896,28 @@ void RenderContextVulkanImpl::flush(const FlushDescriptor& desc)
1,
ZERO_OFFSET_32);
m_vk->CmdDraw(commandBuffer,
gpu::GRAD_SPAN_TRI_STRIP_VERTEX_COUNT,
desc.gradSpanCount,
0,
0);
m_vk->CmdBindPipeline(commandBuffer,
VK_PIPELINE_BIND_POINT_GRAPHICS,
m_colorRampPipeline->renderPipeline());
for (auto [chunkInstanceCount, chunkFirstInstance] :
InstanceChunker(desc.gradSpanCount,
0,
m_workarounds.maxInstancesPerRenderPass))
{
m_colorRampPipeline->interruptRenderPassIfNeeded(
commandBuffer,
renderArea,
*m_gradTextureFramebuffer,
chunkInstanceCount,
m_workarounds);
m_vk->CmdDraw(commandBuffer,
gpu::GRAD_SPAN_TRI_STRIP_VERTEX_COUNT,
chunkInstanceCount,
0,
chunkFirstInstance);
}
m_vk->CmdEndRenderPass(commandBuffer);
@@ -3243,7 +3206,8 @@ void RenderContextVulkanImpl::hotloadShaders(
// Delete and replace old shaders
m_colorRampPipeline =
std::make_unique<ColorRampPipeline>(m_pipelineManager.get());
std::make_unique<ColorRampPipeline>(m_pipelineManager.get(),
m_workarounds);
m_tessellatePipeline =
std::make_unique<TessellatePipeline>(m_pipelineManager.get(),
m_workarounds);

View File

@@ -134,21 +134,20 @@ public:
size_t i,
size_t j) override
{
float stops[7] = {0, .147f, .148f, .23f, .67f, .8f, 1};
rive::ColorInt colors[7] = {randColor(),
randColor(),
randColor(),
randColor(),
randColor(),
randColor(),
randColor()};
rive::ColorInt colors[13];
float stops[std::size(colors)];
for (size_t i = 0; i < std::size(colors); ++i)
{
colors[i] = randColor();
stops[i] = static_cast<float>(i) / (std::size(stops) - 1);
}
return factory->makeLinearGradient((i & 1) ? 16 : 0,
(j & 1) ? 16 : 0,
(i & 1) ? 0 : 16,
(j & 1) ? 0 : 16,
colors,
stops,
7);
std::size(colors));
}
};
GMREGISTER(lots_of_grad_spans, return new LotsOfGradSpansGM)

View File

@@ -5,6 +5,7 @@
#include "gm.hpp"
#include "gmutils.hpp"
#include "rive/renderer.hpp"
#include <set>
using namespace rivegm;
using namespace rive;
@@ -20,8 +21,10 @@ static void add_circle(PathBuilder&,
static void draw_spirograph(Renderer*,
RenderPaint*,
RenderPath* circles,
float2 translate,
float2 c,
int depth);
int depth,
std::set<std::tuple<int, int>>* drawnCenters);
// Stress tests the renderer by trying to fit as many tessellation spans into a
// single flush as possible. This will hopefully detect any potential driver
@@ -52,9 +55,7 @@ private:
}
Path circles = pathBuilder.detach();
renderer->save();
renderer->translate(512, 512);
float2 translate = {512, 512};
Paint paint;
paint->style(m_style);
// Use round joins because they use fewer segments when nearly flat, and
@@ -64,24 +65,35 @@ private:
paint->color((m_style == RenderPaintStyle::fill) ? 0xff7500ff
: 0xfffe0089);
renderer->drawPath(circles, paint);
renderer->save();
draw_spirograph(renderer, paint, circles, float2{-R, 0}, 3);
renderer->restore();
renderer->save();
draw_spirograph(renderer, paint, circles, float2{0, R}, 3);
renderer->restore();
renderer->save();
draw_spirograph(renderer, paint, circles, float2{R, 0}, 3);
renderer->restore();
renderer->save();
draw_spirograph(renderer, paint, circles, float2{0, -R}, 3);
renderer->restore();
renderer->restore();
std::set<std::tuple<int, int>> drawnCenters;
draw_spirograph(renderer,
paint,
circles,
translate,
float2{-R, 0},
3,
&drawnCenters);
draw_spirograph(renderer,
paint,
circles,
translate,
float2{0, R},
3,
&drawnCenters);
draw_spirograph(renderer,
paint,
circles,
translate,
float2{R, 0},
3,
&drawnCenters);
draw_spirograph(renderer,
paint,
circles,
translate,
float2{0, -R},
3,
&drawnCenters);
}
const RenderPaintStyle m_style;
@@ -124,16 +136,28 @@ static void add_circle(PathBuilder& pathBuilder,
static void draw_spirograph(Renderer* renderer,
RenderPaint* paint,
RenderPath* circles,
float2 translate,
float2 c,
int depth)
int depth,
std::set<std::tuple<int, int>>* drawnCenters)
{
if (depth <= 0)
{
return;
}
renderer->translate(c.x, c.y);
renderer->drawPath(circles, paint);
translate += c;
// Only draw the circles once, centered within any given pixel. When we draw
// overlapping circles, coverage-based AA darkens but MSAA does not, leading
// to divergence in golden images.
int2 icenter = simd::cast<int>(simd::floor(translate + .5f));
if (drawnCenters->insert({int(icenter.x), int(icenter.y)}).second)
{
AutoRestore ar(renderer, /*doSave=*/true);
renderer->translate(translate.x, translate.y);
renderer->drawPath(circles, paint);
}
// Next draw circles centered on both points of intersection between the
// circle we just drew and the one before it!
@@ -166,8 +190,20 @@ static void draw_spirograph(Renderer* renderer,
right = right.yx;
}
draw_spirograph(renderer, paint, circles, left, depth - 1);
draw_spirograph(renderer, paint, circles, right, depth - 1);
draw_spirograph(renderer,
paint,
circles,
translate,
left,
depth - 1,
drawnCenters);
draw_spirograph(renderer,
paint,
circles,
translate,
right,
depth - 1,
drawnCenters);
}
GMREGISTER(lots_of_tess_spans_stroke,