Add initial (incomplete) version of ShaderRenderer

This new renderer uses a shader to paint only the checkered
background. It can be tested only in ENABLE_DEVMODE and pressing F1
key to switch between the renderers (which is a devmode special key
for testing purposes only)

We've changed the Renderer::renderSprite(Image*, ...) member function
to renderSprite(os::Surface*, ...) so we receive the os::Surface
directly to paint the SkCanvas with a SkShader. Probably more
refactors will be needed.
This commit is contained in:
David Capello 2022-09-16 11:39:19 -03:00
parent a7e155a391
commit be583f8149
10 changed files with 408 additions and 83 deletions

View File

@ -57,7 +57,7 @@ When Aseprite is compiled with `ENABLE_DEVMODE`, you have the
following extra commands/features available:
* `F5`: On Windows shows the amount of used memory.
* `F1`: Switch new/old render engine.
* `F1`: Switch between new/old/shader renderers.
* `Ctrl+F1`: Switch/test Screen/UI Scaling values.
* `Ctrl+Alt+Shift+Q`: crashes the application in case that you want to
test the anticrash feature or your need a memory dump file.

View File

@ -641,6 +641,7 @@ add_library(app-lib
modules/palettes.cpp
pref/preferences.cpp
recent_files.cpp
render/shader_renderer.cpp
render/simple_renderer.cpp
res/palettes_loader_delegate.cpp
res/resources_loader.cpp

View File

@ -15,6 +15,10 @@ namespace doc {
class Sprite;
}
namespace os {
class Surface;
}
namespace app {
// Abstract class to render images from any editor to be displayed
@ -54,7 +58,7 @@ namespace app {
virtual void renderSprite(doc::Image* dstImage,
const doc::Sprite* sprite,
const doc::frame_t frame) = 0;
virtual void renderSprite(doc::Image* dstImage,
virtual void renderSprite(os::Surface* dstSurface,
const doc::Sprite* sprite,
const doc::frame_t frame,
const gfx::ClipF& area) = 0;

View File

@ -0,0 +1,177 @@
// Aseprite
// Copyright (C) 2022 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/render/shader_renderer.h"
#if SK_ENABLE_SKSL
#include "app/color_utils.h"
#include "app/util/shader_helpers.h"
#include "os/skia/skia_surface.h"
#include "include/core/SkCanvas.h"
#include "include/effects/SkRuntimeEffect.h"
namespace app {
static const char* kBgShaderCode = R"(
uniform half3 iRes, iCanvas, iSrcPos;
uniform half4 iBg1, iBg2;
uniform half2 iStripeSize;
half4 main(vec2 fragcoord) {
vec2 u = (iSrcPos.xy+fragcoord.xy) / iStripeSize.xy;
return (mod(mod(floor(u.x), 2) + mod(floor(u.y), 2), 2) != 0.0 ? iBg2: iBg1);
}
)";
ShaderRenderer::ShaderRenderer()
{
auto result = SkRuntimeEffect::MakeForShader(SkString(kBgShaderCode));
if (!result.errorText.isEmpty()) {
LOG(ERROR, "Shader error: %s\n", result.errorText.c_str());
std::printf("Shader error: %s\n", result.errorText.c_str());
throw std::runtime_error("Cannot compile shaders for ShaderRenderer");
}
m_bgEffect = result.effect;
}
ShaderRenderer::~ShaderRenderer() = default;
void ShaderRenderer::setRefLayersVisiblity(const bool visible)
{
// TODO impl
}
void ShaderRenderer::setNonactiveLayersOpacity(const int opacity)
{
// TODO impl
}
void ShaderRenderer::setNewBlendMethod(const bool newBlend)
{
// TODO impl
}
void ShaderRenderer::setBgOptions(const render::BgOptions& bg)
{
m_bgOptions = bg;
}
void ShaderRenderer::setProjection(const render::Projection& projection)
{
// TODO impl
}
void ShaderRenderer::setSelectedLayer(const doc::Layer* layer)
{
// TODO impl
}
void ShaderRenderer::setPreviewImage(const doc::Layer* layer,
const doc::frame_t frame,
const doc::Image* image,
const doc::Tileset* tileset,
const gfx::Point& pos,
const doc::BlendMode blendMode)
{
// TODO impl
}
void ShaderRenderer::removePreviewImage()
{
// TODO impl
}
void ShaderRenderer::setExtraImage(render::ExtraType type,
const doc::Cel* cel,
const doc::Image* image,
const doc::BlendMode blendMode,
const doc::Layer* currentLayer,
const doc::frame_t currentFrame)
{
// TODO impl
}
void ShaderRenderer::removeExtraImage()
{
// TODO impl
}
void ShaderRenderer::setOnionskin(const render::OnionskinOptions& options)
{
// TODO impl
}
void ShaderRenderer::disableOnionskin()
{
// TODO impl
}
void ShaderRenderer::renderSprite(doc::Image* dstImage,
const doc::Sprite* sprite,
const doc::frame_t frame)
{
// TODO impl
}
void ShaderRenderer::renderSprite(os::Surface* dstSurface,
const doc::Sprite* sprite,
const doc::frame_t frame,
const gfx::ClipF& area)
{
SkRuntimeShaderBuilder builder(m_bgEffect);
builder.uniform("iRes") = SkV3{float(area.size.w), float(area.size.h), 0.0f};
builder.uniform("iCanvas") = SkV3{float(sprite->width()), float(sprite->height()), 0.0f};
builder.uniform("iSrcPos") = SkV3{float(area.src.x), float(area.src.y), 0.0f};
builder.uniform("iBg1") = gfxColor_to_SkV4(
color_utils::color_for_ui(
app::Color::fromImage(sprite->pixelFormat(),
m_bgOptions.color1)));
builder.uniform("iBg2") = gfxColor_to_SkV4(
color_utils::color_for_ui(
app::Color::fromImage(sprite->pixelFormat(),
m_bgOptions.color2)));
builder.uniform("iStripeSize") = SkV2{
float(m_bgOptions.stripeSize.w),
float(m_bgOptions.stripeSize.h)};
SkCanvas* canvas = &static_cast<os::SkiaSurface*>(dstSurface)->canvas();
canvas->save();
{
SkPaint p;
p.setStyle(SkPaint::kFill_Style);
p.setShader(builder.makeShader());
canvas->drawRect(SkRect::MakeXYWH(area.dst.x, area.dst.y, area.size.w, area.size.h), p);
}
canvas->restore();
}
void ShaderRenderer::renderCheckeredBackground(doc::Image* dstImage,
const gfx::Clip& area)
{
// TODO impl
}
void ShaderRenderer::renderImage(doc::Image* dstImage,
const doc::Image* srcImage,
const doc::Palette* pal,
const int x,
const int y,
const int opacity,
const doc::BlendMode blendMode)
{
// TODO impl
}
} // namespace app
#endif // SK_ENABLE_SKSL

View File

@ -0,0 +1,78 @@
// Aseprite
// Copyright (C) 2022 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_RENDER_SHADER_RENDERER_H_INCLUDED
#define APP_RENDER_SHADER_RENDERER_H_INCLUDED
#pragma once
#if SK_ENABLE_SKSL
#include "app/render/renderer.h"
#include "include/core/SkRefCnt.h"
class SkRuntimeEffect;
namespace app {
// Use SkSL to compose images with Skia shaders on the CPU (with the
// SkSL VM) or GPU-accelerated (with native OpenGL/Metal/etc. shaders).
class ShaderRenderer : public Renderer {
public:
ShaderRenderer();
~ShaderRenderer();
void setRefLayersVisiblity(const bool visible) override;
void setNonactiveLayersOpacity(const int opacity) override;
void setNewBlendMethod(const bool newBlend) override;
void setBgOptions(const render::BgOptions& bg) override;
void setProjection(const render::Projection& projection) override;
void setSelectedLayer(const doc::Layer* layer) override;
void setPreviewImage(const doc::Layer* layer,
const doc::frame_t frame,
const doc::Image* image,
const doc::Tileset* tileset,
const gfx::Point& pos,
const doc::BlendMode blendMode) override;
void removePreviewImage() override;
void setExtraImage(render::ExtraType type,
const doc::Cel* cel,
const doc::Image* image,
const doc::BlendMode blendMode,
const doc::Layer* currentLayer,
const doc::frame_t currentFrame) override;
void removeExtraImage() override;
void setOnionskin(const render::OnionskinOptions& options) override;
void disableOnionskin() override;
void renderSprite(doc::Image* dstImage,
const doc::Sprite* sprite,
const doc::frame_t frame) override;
void renderSprite(os::Surface* dstSurface,
const doc::Sprite* sprite,
const doc::frame_t frame,
const gfx::ClipF& area) override;
void renderCheckeredBackground(doc::Image* dstImage,
const gfx::Clip& area) override;
void renderImage(doc::Image* dstImage,
const doc::Image* srcImage,
const doc::Palette* pal,
const int x,
const int y,
const int opacity,
const doc::BlendMode blendMode) override;
private:
render::BgOptions m_bgOptions;
sk_sp<SkRuntimeEffect> m_bgEffect;
};
} // namespace app
#endif // SK_ENABLE_SKSL
#endif

View File

@ -10,8 +10,13 @@
#include "app/render/simple_renderer.h"
#include "app/ui/editor/editor_render.h"
#include "app/util/conversion_to_surface.h"
namespace app {
using namespace doc;
void SimpleRenderer::setRefLayersVisiblity(const bool visible)
{
m_render.setRefLayersVisiblity(visible);
@ -91,12 +96,18 @@ void SimpleRenderer::renderSprite(doc::Image* dstImage,
m_render.renderSprite(dstImage, sprite, frame);
}
void SimpleRenderer::renderSprite(doc::Image* dstImage,
void SimpleRenderer::renderSprite(os::Surface* dstSurface,
const doc::Sprite* sprite,
const doc::frame_t frame,
const gfx::ClipF& area)
{
m_render.renderSprite(dstImage, sprite, frame, area);
ImageRef dstImage(Image::create(
IMAGE_RGB, area.size.w, area.size.h,
EditorRender::getRenderImageBuffer()));
m_render.renderSprite(dstImage.get(), sprite, frame, area);
convert_image_to_surface(dstImage.get(), sprite->palette(frame),
dstSurface, 0, 0, 0, 0, area.size.w, area.size.h);
}
void SimpleRenderer::renderCheckeredBackground(doc::Image* dstImage,

View File

@ -44,7 +44,7 @@ namespace app {
void renderSprite(doc::Image* dstImage,
const doc::Sprite* sprite,
const doc::frame_t frame) override;
void renderSprite(doc::Image* dstImage,
void renderSprite(os::Surface* dstSurface,
const doc::Sprite* sprite,
const doc::frame_t frame,
const gfx::ClipF& area) override;

View File

@ -53,7 +53,6 @@
#include "app/ui/timeline/timeline.h"
#include "app/ui/toolbar.h"
#include "app/ui_context.h"
#include "app/util/conversion_to_surface.h"
#include "app/util/layer_utils.h"
#include "base/chrono.h"
#include "base/convert_to.h"
@ -248,6 +247,11 @@ bool Editor::isUsingNewRenderEngine() const
{
ASSERT(m_sprite);
return
// TODO add an option to the ShaderRenderer to works as the "old"
// engine (screen pixel by screen pixel) or as the "new"
// engine (sprite pixel by sprite pixel)
(m_renderEngine->type() == EditorRender::Type::kShaderRenderer)
||
(Preferences::instance().experimental.newRenderEngine()
// Reference layers + zoom > 100% need the old render engine for
// sub-pixel rendering.
@ -656,16 +660,24 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& sprite
dest.h = rc.h;
}
std::unique_ptr<Image> rendered(nullptr);
// Convert the render to a os::Surface
static os::SurfaceRef rendered = nullptr; // TODO move this to other centralized place
try {
// Generate a "expose sprite pixels" notification. This is used by
// tool managers that need to validate this region (copy pixels from
// the original cel) before it can be used by the RenderEngine.
m_document->notifyExposeSpritePixels(m_sprite, gfx::Region(expose));
// Create a temporary RGB bitmap to draw all to it
rendered.reset(Image::create(IMAGE_RGB, rc2.w, rc2.h,
m_renderEngine->getRenderImageBuffer()));
// Create a temporary surface to draw the sprite on it
if (!rendered ||
rendered->width() < rc2.w ||
rendered->height() < rc2.h ||
rendered->colorSpace() != m_document->osColorSpace()) {
const int maxw = std::max(rc2.w, rendered ? rendered->width(): 0);
const int maxh = std::max(rc2.h, rendered ? rendered->height(): 0);
rendered = os::instance()->makeSurface(
maxw, maxh, m_document->osColorSpace());
}
m_renderEngine->setNewBlendMethod(pref.experimental.newBlend());
m_renderEngine->setRefLayersVisiblity(true);
@ -676,7 +688,7 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& sprite
m_renderEngine->setNonactiveLayersOpacity(255);
m_renderEngine->setProjection(
newEngine ? render::Projection(): m_proj);
m_renderEngine->setupBackground(m_document, rendered->pixelFormat());
m_renderEngine->setupBackground(m_document, IMAGE_RGB);
m_renderEngine->disableOnionskin();
if ((m_flags & kShowOnionskin) == kShowOnionskin) {
@ -729,70 +741,36 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& sprite
Console::showException(e);
}
if (rendered) {
// Convert the render to a os::Surface
static os::SurfaceRef tmp = nullptr; // TODO move this to other centralized place
if (!tmp ||
tmp->width() < rc2.w ||
tmp->height() < rc2.h ||
tmp->colorSpace() != m_document->osColorSpace()) {
const int maxw = std::max(rc2.w, tmp ? tmp->width(): 0);
const int maxh = std::max(rc2.h, tmp ? tmp->height(): 0);
tmp = os::instance()->makeSurface(
maxw, maxh, m_document->osColorSpace());
}
if (tmp->nativeHandle()) {
if (newEngine) {
// Without doing something on the "tmp" surface before (like
// just drawing a pixel), we get a strange behavior where
// pixels are not updated correctly on the editor (e.g. when
// zoom < 100%). I didn't have enough time to investigate this
// issue yet, but this is a partial fix/hack.
//
// TODO review why do we need to do this, it looks like some
// internal state of a SkCanvas or SkBitmap thing is
// updated after this, because convert_image_to_surface()
// will overwrite these pixels anyway.
os::Paint paint;
paint.color(gfx::rgba(0, 0, 0, 255));
tmp->drawRect(gfx::Rect(0, 0, 1, 1), paint);
}
convert_image_to_surface(rendered.get(), m_sprite->palette(m_frame),
tmp.get(), 0, 0, 0, 0, rc2.w, rc2.h);
if (newEngine) {
os::Sampling sampling;
if (m_proj.scaleX() < 1.0) {
switch (pref.editor.downsampling()) {
case gen::Downsampling::NEAREST:
sampling = os::Sampling(os::Sampling::Filter::Nearest);
break;
case gen::Downsampling::BILINEAR:
sampling = os::Sampling(os::Sampling::Filter::Linear);
break;
case gen::Downsampling::BILINEAR_MIPMAP:
sampling = os::Sampling(os::Sampling::Filter::Linear,
os::Sampling::Mipmap::Nearest);
break;
case gen::Downsampling::TRILINEAR_MIPMAP:
sampling = os::Sampling(os::Sampling::Filter::Linear,
os::Sampling::Mipmap::Linear);
break;
}
if (rendered && rendered->nativeHandle()) {
if (newEngine) {
os::Sampling sampling;
if (m_proj.scaleX() < 1.0) {
switch (pref.editor.downsampling()) {
case gen::Downsampling::NEAREST:
sampling = os::Sampling(os::Sampling::Filter::Nearest);
break;
case gen::Downsampling::BILINEAR:
sampling = os::Sampling(os::Sampling::Filter::Linear);
break;
case gen::Downsampling::BILINEAR_MIPMAP:
sampling = os::Sampling(os::Sampling::Filter::Linear,
os::Sampling::Mipmap::Nearest);
break;
case gen::Downsampling::TRILINEAR_MIPMAP:
sampling = os::Sampling(os::Sampling::Filter::Linear,
os::Sampling::Mipmap::Linear);
break;
}
}
g->drawSurface(tmp.get(),
gfx::Rect(0, 0, rc2.w, rc2.h),
dest,
sampling,
nullptr);
}
else {
g->blit(tmp.get(), 0, 0, dest.x, dest.y, dest.w, dest.h);
}
g->drawSurface(rendered.get(),
gfx::Rect(0, 0, rc2.w, rc2.h),
dest,
sampling,
nullptr);
}
else {
g->blit(rendered.get(), 0, 0, dest.x, dest.y, dest.w, dest.h);
}
}
@ -2034,15 +2012,52 @@ bool Editor::onProcessMessage(Message* msg)
case kKeyDownMessage:
#if ENABLE_DEVMODE
// Switch render mode
// Switch renderer
if (!msg->ctrlPressed() &&
static_cast<KeyMessage*>(msg)->scancode() == kKeyF1) {
Preferences::instance().experimental.newRenderEngine(
!Preferences::instance().experimental.newRenderEngine());
invalidateCanvas();
// TODO replace this experimental flag with a new enum (or
// maybe there is no need for user option now that the
// new engine allows to disable the bilinear mipmapping
// interpolation) as we still need the "old" engine to
// render reference layers
auto& newRenderEngine = Preferences::instance().experimental.newRenderEngine;
#if SK_ENABLE_SKSL
// Simple (new) -> Simple (old) -> Shader -> Simple (new) -> ...
if (m_renderEngine->type() == EditorRender::Type::kShaderRenderer) {
newRenderEngine(true);
m_renderEngine->setType(EditorRender::Type::kSimpleRenderer);
}
else {
if (newRenderEngine()) {
newRenderEngine(false);
}
else {
newRenderEngine(true);
m_renderEngine->setType(EditorRender::Type::kShaderRenderer);
}
}
#else
// Simple (new) <-> Simple (old)
newRenderEngine(!newRenderEngine());
#endif
switch (m_renderEngine->type()) {
case EditorRender::Type::kSimpleRenderer:
StatusBar::instance()->showTip(
1000, fmt::format("Simple Renderer ({})", newRenderEngine() ? "new": "old"));
break;
case EditorRender::Type::kShaderRenderer:
StatusBar::instance()->showTip(
1000, fmt::format("Shader Renderer"));
break;
}
app_refresh_screen();
return true;
}
#endif
#endif // ENABLE_DEVMODE
if (m_sprite) {
EditorStatePtr holdState(m_state);
bool used = m_state->onKeyDown(this, static_cast<KeyMessage*>(msg));

View File

@ -13,6 +13,7 @@
#include "app/color_utils.h"
#include "app/pref/preferences.h"
#include "app/render/shader_renderer.h"
#include "app/render/simple_renderer.h"
namespace app {
@ -20,6 +21,7 @@ namespace app {
static doc::ImageBufferPtr g_renderBuffer;
EditorRender::EditorRender()
// TODO create a switch in the preferences
: m_renderer(std::make_unique<SimpleRenderer>())
{
m_renderer->setNewBlendMethod(
@ -30,6 +32,31 @@ EditorRender::~EditorRender()
{
}
EditorRender::Type EditorRender::type() const
{
#if SK_ENABLE_SKSL
if (dynamic_cast<ShaderRenderer*>(m_renderer.get()))
return Type::kShaderRenderer;
#endif
return Type::kSimpleRenderer;
}
void EditorRender::setType(const Type type)
{
#if SK_ENABLE_SKSL
if (type == Type::kShaderRenderer) {
m_renderer = std::make_unique<ShaderRenderer>();
}
else
#endif
{
m_renderer = std::make_unique<SimpleRenderer>();
}
m_renderer->setNewBlendMethod(
Preferences::instance().experimental.newBlend());
}
void EditorRender::setRefLayersVisiblity(const bool visible)
{
m_renderer->setRefLayersVisiblity(visible);
@ -157,12 +184,12 @@ void EditorRender::renderSprite(
}
void EditorRender::renderSprite(
doc::Image* dstImage,
os::Surface* dstSurface,
const doc::Sprite* sprite,
doc::frame_t frame,
const gfx::ClipF& area)
{
m_renderer->renderSprite(dstImage, sprite, frame, area);
m_renderer->renderSprite(dstSurface, sprite, frame, area);
}
void EditorRender::renderCheckeredBackground(
@ -184,7 +211,7 @@ void EditorRender::renderImage(
m_renderer->renderImage(dst_image, src_image, pal,
x, y, opacity, blendMode);
}
// static
doc::ImageBufferPtr EditorRender::getRenderImageBuffer()
{
if (!g_renderBuffer)

View File

@ -29,15 +29,27 @@ namespace doc {
class Tileset;
}
namespace os {
class Surface;
}
namespace app {
class Doc;
class Renderer;
class EditorRender {
public:
enum Type {
kSimpleRenderer,
kShaderRenderer,
};
EditorRender();
~EditorRender();
Type type() const;
void setType(const Type type);
void setRefLayersVisiblity(const bool visible);
void setNonactiveLayersOpacity(const int opacity);
void setNewBlendMethod(const bool newBlend);
@ -74,7 +86,7 @@ namespace app {
const doc::Sprite* sprite,
doc::frame_t frame);
void renderSprite(
doc::Image* dstImage,
os::Surface* dstSurface,
const doc::Sprite* sprite,
doc::frame_t frame,
const gfx::ClipF& area);
@ -90,7 +102,7 @@ namespace app {
const int opacity,
const doc::BlendMode blendMode);
doc::ImageBufferPtr getRenderImageBuffer();
static doc::ImageBufferPtr getRenderImageBuffer();
private:
std::unique_ptr<Renderer> m_renderer;