• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2 * Copyright 2019 Google LLC
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7 
8 #include "tools/viewer/SkSLSlide.h"
9 
10 #include "include/core/SkCanvas.h"
11 #include "include/core/SkFont.h"
12 #include "include/effects/SkGradientShader.h"
13 #include "include/effects/SkPerlinNoiseShader.h"
14 #include "src/core/SkEnumerate.h"
15 #include "tools/Resources.h"
16 #include "tools/viewer/Viewer.h"
17 
18 #include <algorithm>
19 #include <cstdio>
20 #include "imgui.h"
21 
22 using namespace sk_app;
23 
24 ///////////////////////////////////////////////////////////////////////////////
25 
InputTextCallback(ImGuiInputTextCallbackData * data)26 static int InputTextCallback(ImGuiInputTextCallbackData* data) {
27     if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) {
28         SkString* s = (SkString*)data->UserData;
29         SkASSERT(data->Buf == s->writable_str());
30         SkString tmp(data->Buf, data->BufTextLen);
31         s->swap(tmp);
32         data->Buf = s->writable_str();
33     }
34     return 0;
35 }
36 
SkSLSlide()37 SkSLSlide::SkSLSlide() {
38     // Register types for serialization
39     fName = "SkSL";
40 
41     fSkSL =
42 
43         "uniform shader child;\n"
44         "\n"
45         "half4 main(float2 p) {\n"
46         "    return child.eval(p);\n"
47         "}\n";
48 
49     fCodeIsDirty = true;
50 }
51 
load(SkScalar winWidth,SkScalar winHeight)52 void SkSLSlide::load(SkScalar winWidth, SkScalar winHeight) {
53     SkPoint points[] = { { 0, 0 }, { 256, 0 } };
54     SkColor colors[] = { SK_ColorRED, SK_ColorGREEN };
55 
56     sk_sp<SkShader> shader;
57 
58     fShaders.push_back(std::make_pair("Null", nullptr));
59 
60     shader = SkGradientShader::MakeLinear(points, colors, nullptr, 2, SkTileMode::kClamp);
61     fShaders.push_back(std::make_pair("Linear Gradient", shader));
62 
63     shader = SkGradientShader::MakeRadial({ 256, 256 }, 256, colors, nullptr, 2,
64                                           SkTileMode::kClamp);
65     fShaders.push_back(std::make_pair("Radial Gradient", shader));
66 
67     shader = SkGradientShader::MakeSweep(256, 256, colors, nullptr, 2);
68     fShaders.push_back(std::make_pair("Sweep Gradient", shader));
69 
70     shader = GetResourceAsImage("images/mandrill_256.png")->makeShader(SkSamplingOptions());
71     fShaders.push_back(std::make_pair("Mandrill", shader));
72 
73     fResolution = { winWidth, winHeight, 1.0f };
74 }
75 
unload()76 void SkSLSlide::unload() {
77     fEffect.reset();
78     fInputs.reset();
79     fChildren.reset();
80     fShaders.reset();
81 }
82 
rebuild()83 bool SkSLSlide::rebuild() {
84     // Some of the standard shadertoy inputs:
85     SkString sksl("uniform float3 iResolution;\n"
86                   "uniform float  iTime;\n"
87                   "uniform float4 iMouse;\n");
88     sksl.append(fSkSL);
89 
90     // It shouldn't happen, but it's possible to assert in the compiler, especially mid-edit.
91     // To guard against losing your work, write out the shader to a backup file, then remove it
92     // when we compile successfully.
93     constexpr char kBackupFile[] = "sksl.bak";
94     FILE* backup = fopen(kBackupFile, "w");
95     if (backup) {
96         fwrite(fSkSL.c_str(), 1, fSkSL.size(), backup);
97         fclose(backup);
98     }
99     auto [effect, errorText] = SkRuntimeEffect::MakeForShader(sksl);
100     if (backup) {
101         std::remove(kBackupFile);
102     }
103 
104     if (!effect) {
105         Viewer::ShaderErrorHandler()->compileError(sksl.c_str(), errorText.c_str());
106         return false;
107     }
108 
109     size_t oldSize = fEffect ? fEffect->uniformSize() : 0;
110     fInputs.realloc(effect->uniformSize());
111     if (effect->uniformSize() > oldSize) {
112         memset(fInputs.get() + oldSize, 0, effect->uniformSize() - oldSize);
113     }
114     fChildren.resize_back(effect->children().size());
115 
116     fEffect = effect;
117     fCodeIsDirty = false;
118     return true;
119 }
120 
draw(SkCanvas * canvas)121 void SkSLSlide::draw(SkCanvas* canvas) {
122     canvas->clear(SK_ColorWHITE);
123 
124     ImGui::Begin("SkSL", nullptr, ImGuiWindowFlags_AlwaysVerticalScrollbar);
125 
126     // Edit box for shader code
127     ImGuiInputTextFlags flags = ImGuiInputTextFlags_CallbackResize;
128     ImVec2 boxSize(-1.0f, ImGui::GetTextLineHeight() * 30);
129     if (ImGui::InputTextMultiline("Code", fSkSL.writable_str(), fSkSL.size() + 1, boxSize, flags,
130                                   InputTextCallback, &fSkSL)) {
131         fCodeIsDirty = true;
132     }
133 
134     if (fCodeIsDirty || !fEffect) {
135         this->rebuild();
136     }
137 
138     if (!fEffect) {
139         ImGui::End();
140         return;
141     }
142 
143     // Update fMousePos
144     ImVec2 mousePos = ImGui::GetMousePos();
145     if (ImGui::IsMouseDown(0)) {
146         fMousePos.x = mousePos.x;
147         fMousePos.y = mousePos.y;
148     }
149     if (ImGui::IsMouseClicked(0)) {
150         fMousePos.z = mousePos.x;
151         fMousePos.w = mousePos.y;
152     }
153     fMousePos.z = abs(fMousePos.z) * (ImGui::IsMouseDown(0)    ? 1 : -1);
154     fMousePos.w = abs(fMousePos.w) * (ImGui::IsMouseClicked(0) ? 1 : -1);
155 
156     for (const auto& v : fEffect->uniforms()) {
157         char* data = fInputs.get() + v.offset;
158         if (v.name.equals("iResolution")) {
159             memcpy(data, &fResolution, sizeof(fResolution));
160             continue;
161         }
162         if (v.name.equals("iTime")) {
163             memcpy(data, &fSeconds, sizeof(fSeconds));
164             continue;
165         }
166         if (v.name.equals("iMouse")) {
167             memcpy(data, &fMousePos, sizeof(fMousePos));
168             continue;
169         }
170         switch (v.type) {
171             case SkRuntimeEffect::Uniform::Type::kFloat:
172             case SkRuntimeEffect::Uniform::Type::kFloat2:
173             case SkRuntimeEffect::Uniform::Type::kFloat3:
174             case SkRuntimeEffect::Uniform::Type::kFloat4: {
175                 int rows = ((int)v.type - (int)SkRuntimeEffect::Uniform::Type::kFloat) + 1;
176                 float* f = reinterpret_cast<float*>(data);
177                 for (int c = 0; c < v.count; ++c, f += rows) {
178                     SkString name = v.isArray() ? SkStringPrintf("%s[%d]", v.name.c_str(), c)
179                                                 : v.name;
180                     ImGui::PushID(c);
181                     ImGui::DragScalarN(name.c_str(), ImGuiDataType_Float, f, rows, 1.0f);
182                     ImGui::PopID();
183                 }
184                 break;
185             }
186             case SkRuntimeEffect::Uniform::Type::kFloat2x2:
187             case SkRuntimeEffect::Uniform::Type::kFloat3x3:
188             case SkRuntimeEffect::Uniform::Type::kFloat4x4: {
189                 int rows = ((int)v.type - (int)SkRuntimeEffect::Uniform::Type::kFloat2x2) + 2;
190                 int cols = rows;
191                 float* f = reinterpret_cast<float*>(data);
192                 for (int e = 0; e < v.count; ++e) {
193                     for (int c = 0; c < cols; ++c, f += rows) {
194                         SkString name = v.isArray()
195                             ? SkStringPrintf("%s[%d][%d]", v.name.c_str(), e, c)
196                             : SkStringPrintf("%s[%d]", v.name.c_str(), c);
197                         ImGui::DragScalarN(name.c_str(), ImGuiDataType_Float, f, rows, 1.0f);
198                     }
199                 }
200                 break;
201             }
202             case SkRuntimeEffect::Uniform::Type::kInt:
203             case SkRuntimeEffect::Uniform::Type::kInt2:
204             case SkRuntimeEffect::Uniform::Type::kInt3:
205             case SkRuntimeEffect::Uniform::Type::kInt4: {
206                 int rows = ((int)v.type - (int)SkRuntimeEffect::Uniform::Type::kInt) + 1;
207                 int* i = reinterpret_cast<int*>(data);
208                 for (int c = 0; c < v.count; ++c, i += rows) {
209                     SkString name = v.isArray() ? SkStringPrintf("%s[%d]", v.name.c_str(), c)
210                                                 : v.name;
211                     ImGui::PushID(c);
212                     ImGui::DragScalarN(name.c_str(), ImGuiDataType_S32, i, rows, 1.0f);
213                     ImGui::PopID();
214                 }
215                 break;
216             }
217         }
218     }
219 
220     for (const auto& c : fEffect->children()) {
221         auto curShader =
222                 std::find_if(fShaders.begin(), fShaders.end(), [tgt = fChildren[c.index]](auto p) {
223                     return p.second == tgt;
224                 });
225         SkASSERT(curShader != fShaders.end());
226 
227         if (ImGui::BeginCombo(c.name.c_str(), curShader->first)) {
228             for (const auto& namedShader : fShaders) {
229                 if (ImGui::Selectable(namedShader.first, curShader->second == namedShader.second)) {
230                     fChildren[c.index] = namedShader.second;
231                 }
232             }
233             ImGui::EndCombo();
234         }
235     }
236 
237     static SkColor4f gPaintColor { 1.0f, 1.0f, 1.0f , 1.0f };
238     ImGui::ColorEdit4("Paint Color", gPaintColor.vec());
239 
240     ImGui::RadioButton("Fill",      &fGeometry, kFill);      ImGui::SameLine();
241     ImGui::RadioButton("Circle",    &fGeometry, kCircle);    ImGui::SameLine();
242     ImGui::RadioButton("RoundRect", &fGeometry, kRoundRect); ImGui::SameLine();
243     ImGui::RadioButton("Capsule",   &fGeometry, kCapsule);   ImGui::SameLine();
244     ImGui::RadioButton("Text",      &fGeometry, kText);
245 
246     ImGui::End();
247 
248     auto inputs = SkData::MakeWithoutCopy(fInputs.get(), fEffect->uniformSize());
249     auto shader = fEffect->makeShader(std::move(inputs), fChildren.data(), fChildren.count(),
250                                       nullptr, false);
251 
252     SkPaint p;
253     p.setColor4f(gPaintColor);
254     p.setShader(std::move(shader));
255 
256     switch (fGeometry) {
257         case kFill:
258             canvas->drawPaint(p);
259             break;
260         case kCircle:
261             canvas->drawCircle({ 256, 256 }, 256, p);
262             break;
263         case kRoundRect:
264             canvas->drawRoundRect({ 0, 0, 512, 512 }, 64, 64, p);
265             break;
266         case kCapsule:
267             canvas->drawRoundRect({ 0, 224, 512, 288 }, 32, 32, p);
268             break;
269         case kText: {
270             SkFont font;
271             font.setSize(SkIntToScalar(96));
272             canvas->drawSimpleText("Hello World", strlen("Hello World"), SkTextEncoding::kUTF8, 0,
273                                    256, font, p);
274         } break;
275         default: break;
276     }
277 }
278 
animate(double nanos)279 bool SkSLSlide::animate(double nanos) {
280     fSeconds = static_cast<float>(nanos * 1E-9);
281     return true;
282 }
283