c9125aa8f3
This is a reland of adadb95a9f
... adds a temporary workaround for some Android framework code.
Original change's description:
> Better first-class shader & color filter support in runtime effects
>
> This does a few things, because they're all intertwined:
>
> 1) SkRuntimeEffect's API now includes details about children (which Skia
> stage was declared, not just the name). The factories verify that the
> declared types in the SkSL match up with the C++ types being passed.
> Today, we still only support adding children of the same type, so the
> checks are simple. Once we allow mixing types, we'll be testing the
> declared type against the actual C++ type supplied for each slot.
> 2) Adds sample variants that supply the input color to the child. This
> is now the only way to invoke a colorFilter child. Internally, we
> support passing a color when invoking a child shader, but I'm not
> exposing that. It's not clearly part of the semantics of the Skia
> pipeline, and is almost never useful. It also exposes users to
> several inconsistencies (skbug.com/11942).
> 3) Because of #2, it's possible that we can't compute a reusable program
> to filter individual colors. In that case, we don't set the constant
> output for constant input optimization, and filterColor4f falls back
> to the slower base-class implementation.
>
> Bug: skia:11813 skia:11942
> Change-Id: I06c41e1b35056e486f3163a72acf6b9535d7fed4
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/401917
> Commit-Queue: Brian Osman <brianosman@google.com>
> Reviewed-by: Mike Klein <mtklein@google.com>
Bug: skia:11813 skia:11942
Change-Id: I2c31b147ed86fa8c4dddefb7066bc1d07fe0d285
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/404637
Reviewed-by: Brian Salomon <bsalomon@google.com>
Reviewed-by: Mike Klein <mtklein@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
283 lines
9.7 KiB
C++
283 lines
9.7 KiB
C++
/*
|
|
* Copyright 2019 Google LLC
|
|
*
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include "tools/viewer/SkSLSlide.h"
|
|
|
|
#include "include/core/SkCanvas.h"
|
|
#include "include/core/SkFont.h"
|
|
#include "include/effects/SkGradientShader.h"
|
|
#include "include/effects/SkPerlinNoiseShader.h"
|
|
#include "src/core/SkEnumerate.h"
|
|
#include "tools/Resources.h"
|
|
#include "tools/viewer/Viewer.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstdio>
|
|
#include "imgui.h"
|
|
|
|
using namespace sk_app;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
static int InputTextCallback(ImGuiInputTextCallbackData* data) {
|
|
if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) {
|
|
SkString* s = (SkString*)data->UserData;
|
|
SkASSERT(data->Buf == s->writable_str());
|
|
SkString tmp(data->Buf, data->BufTextLen);
|
|
s->swap(tmp);
|
|
data->Buf = s->writable_str();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
SkSLSlide::SkSLSlide() {
|
|
// Register types for serialization
|
|
fName = "SkSL";
|
|
|
|
fSkSL =
|
|
|
|
"uniform shader child;\n"
|
|
"\n"
|
|
"half4 main(float2 p) {\n"
|
|
" return sample(child, p);\n"
|
|
"}\n";
|
|
|
|
fCodeIsDirty = true;
|
|
}
|
|
|
|
void SkSLSlide::load(SkScalar winWidth, SkScalar winHeight) {
|
|
SkPoint points[] = { { 0, 0 }, { 256, 0 } };
|
|
SkColor colors[] = { SK_ColorRED, SK_ColorGREEN };
|
|
|
|
sk_sp<SkShader> shader;
|
|
|
|
fShaders.push_back(std::make_pair("Null", nullptr));
|
|
|
|
shader = SkGradientShader::MakeLinear(points, colors, nullptr, 2, SkTileMode::kClamp);
|
|
fShaders.push_back(std::make_pair("Linear Gradient", shader));
|
|
|
|
shader = SkGradientShader::MakeRadial({ 256, 256 }, 256, colors, nullptr, 2,
|
|
SkTileMode::kClamp);
|
|
fShaders.push_back(std::make_pair("Radial Gradient", shader));
|
|
|
|
shader = SkGradientShader::MakeSweep(256, 256, colors, nullptr, 2);
|
|
fShaders.push_back(std::make_pair("Sweep Gradient", shader));
|
|
|
|
shader = GetResourceAsImage("images/mandrill_256.png")->makeShader(SkSamplingOptions());
|
|
fShaders.push_back(std::make_pair("Mandrill", shader));
|
|
|
|
fResolution = { winWidth, winHeight, 1.0f };
|
|
}
|
|
|
|
void SkSLSlide::unload() {
|
|
fEffect.reset();
|
|
fInputs.reset();
|
|
fChildren.reset();
|
|
fShaders.reset();
|
|
}
|
|
|
|
bool SkSLSlide::rebuild() {
|
|
// Some of the standard shadertoy inputs:
|
|
SkString sksl("uniform float3 iResolution;\n"
|
|
"uniform float iTime;\n"
|
|
"uniform float4 iMouse;\n");
|
|
sksl.append(fSkSL);
|
|
|
|
// It shouldn't happen, but it's possible to assert in the compiler, especially mid-edit.
|
|
// To guard against losing your work, write out the shader to a backup file, then remove it
|
|
// when we compile successfully.
|
|
constexpr char kBackupFile[] = "sksl.bak";
|
|
FILE* backup = fopen(kBackupFile, "w");
|
|
if (backup) {
|
|
fwrite(fSkSL.c_str(), 1, fSkSL.size(), backup);
|
|
fclose(backup);
|
|
}
|
|
auto [effect, errorText] = SkRuntimeEffect::MakeForShader(sksl);
|
|
if (backup) {
|
|
std::remove(kBackupFile);
|
|
}
|
|
|
|
if (!effect) {
|
|
Viewer::ShaderErrorHandler()->compileError(sksl.c_str(), errorText.c_str());
|
|
return false;
|
|
}
|
|
|
|
size_t oldSize = fEffect ? fEffect->uniformSize() : 0;
|
|
fInputs.realloc(effect->uniformSize());
|
|
if (effect->uniformSize() > oldSize) {
|
|
memset(fInputs.get() + oldSize, 0, effect->uniformSize() - oldSize);
|
|
}
|
|
fChildren.resize_back(effect->children().count());
|
|
|
|
fEffect = effect;
|
|
fCodeIsDirty = false;
|
|
return true;
|
|
}
|
|
|
|
void SkSLSlide::draw(SkCanvas* canvas) {
|
|
canvas->clear(SK_ColorWHITE);
|
|
|
|
ImGui::Begin("SkSL", nullptr, ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
|
|
|
// Edit box for shader code
|
|
ImGuiInputTextFlags flags = ImGuiInputTextFlags_CallbackResize;
|
|
ImVec2 boxSize(-1.0f, ImGui::GetTextLineHeight() * 30);
|
|
if (ImGui::InputTextMultiline("Code", fSkSL.writable_str(), fSkSL.size() + 1, boxSize, flags,
|
|
InputTextCallback, &fSkSL)) {
|
|
fCodeIsDirty = true;
|
|
}
|
|
|
|
if (fCodeIsDirty || !fEffect) {
|
|
this->rebuild();
|
|
}
|
|
|
|
if (!fEffect) {
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
// Update fMousePos
|
|
ImVec2 mousePos = ImGui::GetMousePos();
|
|
if (ImGui::IsMouseDown(0)) {
|
|
fMousePos.x = mousePos.x;
|
|
fMousePos.y = mousePos.y;
|
|
}
|
|
if (ImGui::IsMouseClicked(0)) {
|
|
fMousePos.z = mousePos.x;
|
|
fMousePos.w = mousePos.y;
|
|
}
|
|
fMousePos.z = abs(fMousePos.z) * (ImGui::IsMouseDown(0) ? 1 : -1);
|
|
fMousePos.w = abs(fMousePos.w) * (ImGui::IsMouseClicked(0) ? 1 : -1);
|
|
|
|
for (const auto& v : fEffect->uniforms()) {
|
|
char* data = fInputs.get() + v.offset;
|
|
if (v.name.equals("iResolution")) {
|
|
memcpy(data, &fResolution, sizeof(fResolution));
|
|
continue;
|
|
}
|
|
if (v.name.equals("iTime")) {
|
|
memcpy(data, &fSeconds, sizeof(fSeconds));
|
|
continue;
|
|
}
|
|
if (v.name.equals("iMouse")) {
|
|
memcpy(data, &fMousePos, sizeof(fMousePos));
|
|
continue;
|
|
}
|
|
switch (v.type) {
|
|
case SkRuntimeEffect::Uniform::Type::kFloat:
|
|
case SkRuntimeEffect::Uniform::Type::kFloat2:
|
|
case SkRuntimeEffect::Uniform::Type::kFloat3:
|
|
case SkRuntimeEffect::Uniform::Type::kFloat4: {
|
|
int rows = ((int)v.type - (int)SkRuntimeEffect::Uniform::Type::kFloat) + 1;
|
|
float* f = reinterpret_cast<float*>(data);
|
|
for (int c = 0; c < v.count; ++c, f += rows) {
|
|
SkString name = v.isArray() ? SkStringPrintf("%s[%d]", v.name.c_str(), c)
|
|
: v.name;
|
|
ImGui::PushID(c);
|
|
ImGui::DragScalarN(name.c_str(), ImGuiDataType_Float, f, rows, 1.0f);
|
|
ImGui::PopID();
|
|
}
|
|
break;
|
|
}
|
|
case SkRuntimeEffect::Uniform::Type::kFloat2x2:
|
|
case SkRuntimeEffect::Uniform::Type::kFloat3x3:
|
|
case SkRuntimeEffect::Uniform::Type::kFloat4x4: {
|
|
int rows = ((int)v.type - (int)SkRuntimeEffect::Uniform::Type::kFloat2x2) + 2;
|
|
int cols = rows;
|
|
float* f = reinterpret_cast<float*>(data);
|
|
for (int e = 0; e < v.count; ++e) {
|
|
for (int c = 0; c < cols; ++c, f += rows) {
|
|
SkString name = v.isArray()
|
|
? SkStringPrintf("%s[%d][%d]", v.name.c_str(), e, c)
|
|
: SkStringPrintf("%s[%d]", v.name.c_str(), c);
|
|
ImGui::DragScalarN(name.c_str(), ImGuiDataType_Float, f, rows, 1.0f);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SkRuntimeEffect::Uniform::Type::kInt:
|
|
case SkRuntimeEffect::Uniform::Type::kInt2:
|
|
case SkRuntimeEffect::Uniform::Type::kInt3:
|
|
case SkRuntimeEffect::Uniform::Type::kInt4: {
|
|
int rows = ((int)v.type - (int)SkRuntimeEffect::Uniform::Type::kInt) + 1;
|
|
int* i = reinterpret_cast<int*>(data);
|
|
for (int c = 0; c < v.count; ++c, i += rows) {
|
|
SkString name = v.isArray() ? SkStringPrintf("%s[%d]", v.name.c_str(), c)
|
|
: v.name;
|
|
ImGui::PushID(c);
|
|
ImGui::DragScalarN(name.c_str(), ImGuiDataType_S32, i, rows, 1.0f);
|
|
ImGui::PopID();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const auto& c : fEffect->children()) {
|
|
auto curShader =
|
|
std::find_if(fShaders.begin(), fShaders.end(), [tgt = fChildren[c.index]](auto p) {
|
|
return p.second == tgt;
|
|
});
|
|
SkASSERT(curShader != fShaders.end());
|
|
|
|
if (ImGui::BeginCombo(c.name.c_str(), curShader->first)) {
|
|
for (const auto& namedShader : fShaders) {
|
|
if (ImGui::Selectable(namedShader.first, curShader->second == namedShader.second)) {
|
|
fChildren[c.index] = namedShader.second;
|
|
}
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
}
|
|
|
|
static SkColor4f gPaintColor { 1.0f, 1.0f, 1.0f , 1.0f };
|
|
ImGui::ColorEdit4("Paint Color", gPaintColor.vec());
|
|
|
|
ImGui::RadioButton("Fill", &fGeometry, kFill); ImGui::SameLine();
|
|
ImGui::RadioButton("Circle", &fGeometry, kCircle); ImGui::SameLine();
|
|
ImGui::RadioButton("RoundRect", &fGeometry, kRoundRect); ImGui::SameLine();
|
|
ImGui::RadioButton("Capsule", &fGeometry, kCapsule); ImGui::SameLine();
|
|
ImGui::RadioButton("Text", &fGeometry, kText);
|
|
|
|
ImGui::End();
|
|
|
|
auto inputs = SkData::MakeWithoutCopy(fInputs.get(), fEffect->uniformSize());
|
|
auto shader = fEffect->makeShader(std::move(inputs), fChildren.data(), fChildren.count(),
|
|
nullptr, false);
|
|
|
|
SkPaint p;
|
|
p.setColor4f(gPaintColor);
|
|
p.setShader(std::move(shader));
|
|
|
|
switch (fGeometry) {
|
|
case kFill:
|
|
canvas->drawPaint(p);
|
|
break;
|
|
case kCircle:
|
|
canvas->drawCircle({ 256, 256 }, 256, p);
|
|
break;
|
|
case kRoundRect:
|
|
canvas->drawRoundRect({ 0, 0, 512, 512 }, 64, 64, p);
|
|
break;
|
|
case kCapsule:
|
|
canvas->drawRoundRect({ 0, 224, 512, 288 }, 32, 32, p);
|
|
break;
|
|
case kText: {
|
|
SkFont font;
|
|
font.setSize(SkIntToScalar(96));
|
|
canvas->drawSimpleText("Hello World", strlen("Hello World"), SkTextEncoding::kUTF8, 0,
|
|
256, font, p);
|
|
} break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
bool SkSLSlide::animate(double nanos) {
|
|
fSeconds = static_cast<float>(nanos * 1E-9);
|
|
return true;
|
|
}
|