| /* | |
| * Copyright 2022 Rive | |
| */ | |
| #include "gm.hpp" | |
| #include "gmutils.hpp" | |
| #include "common/rand.hpp" | |
| #include "common/write_png_file.hpp" | |
| #include "rive/shapes/paint/image_sampler.hpp" | |
| #ifndef RIVE_TOOLS_NO_GL | |
| #include "rive/renderer/gl/render_context_gl_impl.hpp" | |
| #endif | |
| using namespace rivegm; | |
| constexpr static size_t kRows = 100; | |
| constexpr static size_t kCols = 100; | |
| // Base class for stress tests that draw huge amounts of something. | |
| class LotsOfSquaresGM : public GM | |
| { | |
| public: | |
| LotsOfSquaresGM() : GM(1701, 1701) {} | |
| rive::ColorInt clearColor() const override { return 0xff000000; } | |
| void onDraw(rive::Renderer* renderer) override | |
| { | |
| preDraw(renderer); | |
| renderer->translate(0, 1); | |
| for (size_t j = 0; j < kRows; ++j) | |
| { | |
| renderer->save(); | |
| for (size_t i = 0; i < kCols; ++i) | |
| { | |
| drawSquare(renderer, 16, i, j); | |
| renderer->translate(17, 0); | |
| } | |
| renderer->restore(); | |
| renderer->translate(0, 17); | |
| } | |
| } | |
| virtual void preDraw(rive::Renderer*) {} | |
| virtual void drawSquare(rive::Renderer*, float size, size_t i, size_t j) = 0; | |
| protected: | |
| rive::ColorInt randColor() | |
| { | |
| return static_cast<rive::ColorInt>(m_rand.u64()) | 0x80000000; | |
| } | |
| Rand m_rand; | |
| }; | |
| // Stress-test the gradient library by drawing more color ramps than fit in the | |
| // gradient texture. | |
| class LotsOfGradsGM : public LotsOfSquaresGM | |
| { | |
| public: | |
| LotsOfGradsGM() : LotsOfSquaresGM() {} | |
| void drawSquare(rive::Renderer* renderer, float size, size_t i, size_t j) | |
| override | |
| { | |
| rive::Factory* factory = TestingWindow::Get()->factory(); | |
| auto paint = factory->makeRenderPaint(); | |
| paint->shader(makeGradient(factory, i, j)); | |
| rivegm::draw_rect(renderer, {0, 0, size, size}, paint.get()); | |
| } | |
| virtual rive::rcp<rive::RenderShader> makeGradient( | |
| rive::Factory*, | |
| size_t i, | |
| size_t j) = 0; | |
| }; | |
| // Run out of "simple" texels in the gradient texture. | |
| class LotsOfGradsSimpleGM : public LotsOfGradsGM | |
| { | |
| public: | |
| LotsOfGradsSimpleGM() : LotsOfGradsGM() {} | |
| rive::rcp<rive::RenderShader> makeGradient( | |
| rive::Factory* factory, | |
| size_t i, | |
| size_t j) override | |
| { | |
| float stops[2] = {0, 1}; | |
| rive::ColorInt colors[2] = {randColor(), randColor()}; | |
| return factory->makeLinearGradient( | |
| (i & 1) ? 16 : 0, | |
| (j & 1) ? 16 : 0, | |
| (i & 1) ? 0 : 16, | |
| (j & 1) ? 0 : 16, | |
| colors, | |
| stops, | |
| 2); | |
| } | |
| }; | |
| GMREGISTER(lots_of_grads_simple, return new LotsOfGradsSimpleGM) | |
| // Run out of "complex" texel rows in the gradient texture. | |
| class LotsOfGradsComplexGM : public LotsOfGradsGM | |
| { | |
| public: | |
| LotsOfGradsComplexGM() : LotsOfGradsGM() {} | |
| rive::rcp<rive::RenderShader> makeGradient( | |
| rive::Factory* factory, | |
| size_t i, | |
| size_t j) override | |
| { | |
| float stops[3] = {.05f, .147f, 1}; | |
| rive::ColorInt colors[3] = {randColor(), randColor(), randColor()}; | |
| return factory->makeRadialGradient( | |
| (i & 1) ? 0 : 16, | |
| (j & 1) ? 2 : 22, | |
| 22, | |
| colors, | |
| stops, | |
| 3); | |
| } | |
| }; | |
| GMREGISTER(lots_of_grads_complex, return new LotsOfGradsComplexGM) | |
| // Run out of "GradSpan" instances for rendering color ramps. | |
| class LotsOfGradSpansGM : public LotsOfGradsGM | |
| { | |
| public: | |
| LotsOfGradSpansGM() : LotsOfGradsGM() {} | |
| rive::rcp<rive::RenderShader> makeGradient( | |
| rive::Factory* factory, | |
| size_t i, | |
| size_t j) override | |
| { | |
| 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, | |
| std::size(colors)); | |
| } | |
| }; | |
| GMREGISTER(lots_of_grad_spans, return new LotsOfGradSpansGM) | |
| // Ensure that an intermediate flush does not invalidate the clip buffer | |
| // contents. | |
| class LotsOfGradsClippedGM : public LotsOfGradSpansGM | |
| { | |
| public: | |
| LotsOfGradsClippedGM() : LotsOfGradSpansGM() {} | |
| void preDraw(rive::Renderer* renderer) override | |
| { | |
| Path circle; | |
| path_addOval(circle, rive::AABB(-1700, -1700, 3400, 3400)); | |
| renderer->clipPath(circle); | |
| } | |
| }; | |
| GMREGISTER(lots_of_grads_clipped, return new LotsOfGradsClippedGM) | |
| // Run out of complex and simple gradients both. | |
| class LotsOfGradsMixedGM : public LotsOfGradsGM | |
| { | |
| public: | |
| LotsOfGradsMixedGM() : LotsOfGradsGM() {} | |
| rive::rcp<rive::RenderShader> makeGradient( | |
| rive::Factory* factory, | |
| size_t i, | |
| size_t j) override | |
| { | |
| rive::ColorInt colors[3] = {randColor(), randColor(), randColor()}; | |
| std::array<float, 3> stops; | |
| // Also test gradients with only one color stop (which render as a solid | |
| // color). | |
| int count = m_rand.u32(1, 3); | |
| if (count == 3) | |
| stops = {.05f, .147f, 1}; | |
| else | |
| stops = {0, 1}; | |
| if (m_rand.boolean()) | |
| { | |
| return factory->makeRadialGradient( | |
| (i & 1) ? 0 : 16, | |
| (j & 1) ? 2 : 22, | |
| 22, | |
| colors, | |
| stops.data(), | |
| count); | |
| } | |
| else | |
| { | |
| return factory->makeLinearGradient( | |
| (i & 1) ? 16 : 0, | |
| (j & 1) ? 16 : 0, | |
| (i & 1) ? 0 : 16, | |
| (j & 1) ? 0 : 16, | |
| colors, | |
| stops.data(), | |
| count); | |
| } | |
| } | |
| }; | |
| GMREGISTER(lots_of_grads_mixed, return new LotsOfGradsMixedGM) | |
| // Run out of VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE descriptors, requiring the Vulkan | |
| // backend to allocate a new VkDescriptorPool during flush. | |
| class LotsOfImagesGM : public LotsOfSquaresGM | |
| { | |
| public: | |
| LotsOfImagesGM() : LotsOfSquaresGM() {} | |
| void drawSquare(rive::Renderer* renderer, float size, size_t i, size_t j) | |
| override | |
| { | |
| size_t imageCount = std::size(m_images); | |
| auto& image = m_images[(j * kCols + i) % imageCount]; | |
| if (image == nullptr) | |
| { | |
| constexpr static size_t pngSize = 4; | |
| rive::Factory* factory = TestingWindow::Get()->factory(); | |
| auto color = randColor(); | |
| std::vector<uint8_t> imagePixels(pngSize * pngSize * 4); | |
| for (auto it = imagePixels.begin(); it != imagePixels.end(); it += 4) | |
| { | |
| rive::UnpackColorToRGBA8(color, &(*it)); | |
| } | |
| std::vector<uint8_t> encodedData = EncodePNGToBuffer( | |
| pngSize, | |
| pngSize, | |
| imagePixels.data(), | |
| PNGCompression::fast_rle); | |
| image = factory->decodeImage(rive::Span<uint8_t>(encodedData)); | |
| if (image == nullptr) | |
| { | |
| return; | |
| } | |
| } | |
| AutoRestore ar(renderer, /*doSave=*/true); | |
| renderer->scale(size / image->width(), size / image->height()); | |
| renderer->drawImage( | |
| image.get(), | |
| rive::ImageSampler::LinearClamp(), | |
| rive::BlendMode::srcOver, | |
| 1); | |
| } | |
| private: | |
| rive::rcp<rive::RenderImage> m_images[512]; | |
| }; | |
| GMREGISTER(lots_of_images, return new LotsOfImagesGM) | |
| // Run out of kMaxImageTextureUpdates descriptors, requiring the Vulkan | |
| // backend to allocate a new VkDescriptorPool during flush. | |
| class LotsOfImagesSampledGM : public LotsOfSquaresGM | |
| { | |
| public: | |
| LotsOfImagesSampledGM() : LotsOfSquaresGM() {} | |
| void drawSquare(rive::Renderer* renderer, float size, size_t i, size_t j) | |
| override | |
| { | |
| auto flatIndex = (j * kCols + i); | |
| auto sampler = rive::ImageSampler::SamplerFromKey( | |
| flatIndex % rive::ImageSampler::MAX_SAMPLER_PERMUTATIONS); | |
| if (m_image == nullptr) | |
| { | |
| constexpr static size_t pngSize = 4; | |
| rive::Factory* factory = TestingWindow::Get()->factory(); | |
| auto color = randColor(); | |
| std::vector<uint8_t> imagePixels(pngSize * pngSize * 4); | |
| for (auto it = imagePixels.begin(); it != imagePixels.end(); it += 4) | |
| { | |
| rive::UnpackColorToRGBA8(color, &(*it)); | |
| } | |
| std::vector<uint8_t> encodedData = EncodePNGToBuffer( | |
| pngSize, | |
| pngSize, | |
| imagePixels.data(), | |
| PNGCompression::fast_rle); | |
| m_image = factory->decodeImage(rive::Span<uint8_t>(encodedData)); | |
| if (m_image == nullptr) | |
| { | |
| return; | |
| } | |
| } | |
| AutoRestore ar(renderer, /*doSave=*/true); | |
| renderer->scale(size / m_image->width(), size / m_image->height()); | |
| renderer->drawImage(m_image.get(), sampler, rive::BlendMode::srcOver, 1); | |
| } | |
| private: | |
| rive::rcp<rive::RenderImage> m_image; | |
| }; | |
| GMREGISTER(lots_of_images_sampled, return new LotsOfImagesSampledGM) |