• 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 "include/sksl/SkSLDebugTrace.h"
15 #include "src/core/SkEnumerate.h"
16 #include "tools/Resources.h"
17 #include "tools/viewer/Viewer.h"
18 
19 #include <algorithm>
20 #include <cstdio>
21 #include "imgui.h"
22 
23 using namespace sk_app;
24 
25 ///////////////////////////////////////////////////////////////////////////////
26 
InputTextCallback(ImGuiInputTextCallbackData * data)27 static int InputTextCallback(ImGuiInputTextCallbackData* data) {
28     if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) {
29         SkString* s = (SkString*)data->UserData;
30         SkASSERT(data->Buf == s->writable_str());
31         SkString tmp(data->Buf, data->BufTextLen);
32         s->swap(tmp);
33         data->Buf = s->writable_str();
34     }
35     return 0;
36 }
37 
SkSLSlide()38 SkSLSlide::SkSLSlide() {
39     // Register types for serialization
40     fName = "SkSL";
41 
42     fSkSL =
43 
44         "uniform shader child;\n"
45         "\n"
46         "half4 main(float2 p) {\n"
47         "    return child.eval(p);\n"
48         "}\n";
49 
50     fCodeIsDirty = true;
51 }
52 
load(SkScalar winWidth,SkScalar winHeight)53 void SkSLSlide::load(SkScalar winWidth, SkScalar winHeight) {
54     SkPoint points[] = { { 0, 0 }, { 256, 0 } };
55     SkColor colors[] = { SK_ColorRED, SK_ColorGREEN };
56 
57     sk_sp<SkShader> shader;
58 
59     fShaders.push_back(std::make_pair("Null", nullptr));
60 
61     shader = SkGradientShader::MakeLinear(points, colors, nullptr, 2, SkTileMode::kClamp);
62     fShaders.push_back(std::make_pair("Linear Gradient", shader));
63 
64     shader = SkGradientShader::MakeRadial({ 256, 256 }, 256, colors, nullptr, 2,
65                                           SkTileMode::kClamp);
66     fShaders.push_back(std::make_pair("Radial Gradient", shader));
67 
68     shader = SkGradientShader::MakeSweep(256, 256, colors, nullptr, 2);
69     fShaders.push_back(std::make_pair("Sweep Gradient", shader));
70 
71     shader = GetResourceAsImage("images/mandrill_256.png")->makeShader(SkSamplingOptions());
72     fShaders.push_back(std::make_pair("Mandrill", shader));
73 
74     fResolution = { winWidth, winHeight, 1.0f };
75 }
76 
unload()77 void SkSLSlide::unload() {
78     fEffect.reset();
79     fInputs.reset();
80     fChildren.reset();
81     fShaders.reset();
82 }
83 
rebuild()84 bool SkSLSlide::rebuild() {
85     // Some of the standard shadertoy inputs:
86     SkString sksl;
87     if (fShadertoyUniforms) {
88         sksl = "uniform float3 iResolution;\n"
89                "uniform float  iTime;\n"
90                "uniform float4 iMouse;\n";
91     }
92     sksl.append(fSkSL);
93 
94     // It shouldn't happen, but it's possible to assert in the compiler, especially mid-edit.
95     // To guard against losing your work, write out the shader to a backup file, then remove it
96     // when we compile successfully.
97     constexpr char kBackupFile[] = "sksl.bak";
98     FILE* backup = fopen(kBackupFile, "w");
99     if (backup) {
100         fwrite(fSkSL.c_str(), 1, fSkSL.size(), backup);
101         fclose(backup);
102     }
103     auto [effect, errorText] = SkRuntimeEffect::MakeForShader(sksl);
104     if (backup) {
105         std::remove(kBackupFile);
106     }
107 
108     if (!effect) {
109         Viewer::ShaderErrorHandler()->compileError(sksl.c_str(), errorText.c_str());
110         return false;
111     }
112 
113     size_t oldSize = fEffect ? fEffect->uniformSize() : 0;
114     fInputs.realloc(effect->uniformSize());
115     if (effect->uniformSize() > oldSize) {
116         memset(fInputs.get() + oldSize, 0, effect->uniformSize() - oldSize);
117     }
118     fChildren.resize_back(effect->children().size());
119 
120     fEffect = effect;
121     fCodeIsDirty = false;
122     return true;
123 }
124 
draw(SkCanvas * canvas)125 void SkSLSlide::draw(SkCanvas* canvas) {
126     canvas->clear(SK_ColorWHITE);
127 
128     ImGui::Begin("SkSL", nullptr, ImGuiWindowFlags_AlwaysVerticalScrollbar);
129 
130     // Edit box for shader code
131     ImGuiInputTextFlags flags = ImGuiInputTextFlags_CallbackResize;
132     ImVec2 boxSize(-1.0f, ImGui::GetTextLineHeight() * 30);
133     if (ImGui::InputTextMultiline("Code", fSkSL.writable_str(), fSkSL.size() + 1, boxSize, flags,
134                                   InputTextCallback, &fSkSL)) {
135         fCodeIsDirty = true;
136     }
137 
138     if (ImGui::Checkbox("ShaderToy Uniforms (iResolution/iTime/iMouse)", &fShadertoyUniforms)) {
139         fCodeIsDirty = true;
140     }
141 
142     if (fCodeIsDirty || !fEffect) {
143         this->rebuild();
144     }
145 
146     if (!fEffect) {
147         ImGui::End();
148         return;
149     }
150 
151     bool writeTrace = false;
152     bool writeDump = false;
153     if (!canvas->recordingContext()) {
154         ImGui::InputInt2("Trace Coordinate (X/Y)", fTraceCoord);
155         writeTrace = ImGui::Button("Write Debug Trace (JSON)");
156         writeDump = ImGui::Button("Write Debug Dump (Human-Readable)");
157     }
158 
159     // Update fMousePos
160     ImVec2 mousePos = ImGui::GetMousePos();
161     if (ImGui::IsMouseDown(0)) {
162         fMousePos.x = mousePos.x;
163         fMousePos.y = mousePos.y;
164     }
165     if (ImGui::IsMouseClicked(0)) {
166         fMousePos.z = mousePos.x;
167         fMousePos.w = mousePos.y;
168     }
169     fMousePos.z = abs(fMousePos.z) * (ImGui::IsMouseDown(0)    ? 1 : -1);
170     fMousePos.w = abs(fMousePos.w) * (ImGui::IsMouseClicked(0) ? 1 : -1);
171 
172     for (const auto& v : fEffect->uniforms()) {
173         char* data = fInputs.get() + v.offset;
174         if (v.name.equals("iResolution")) {
175             memcpy(data, &fResolution, sizeof(fResolution));
176             continue;
177         }
178         if (v.name.equals("iTime")) {
179             memcpy(data, &fSeconds, sizeof(fSeconds));
180             continue;
181         }
182         if (v.name.equals("iMouse")) {
183             memcpy(data, &fMousePos, sizeof(fMousePos));
184             continue;
185         }
186         switch (v.type) {
187             case SkRuntimeEffect::Uniform::Type::kFloat:
188             case SkRuntimeEffect::Uniform::Type::kFloat2:
189             case SkRuntimeEffect::Uniform::Type::kFloat3:
190             case SkRuntimeEffect::Uniform::Type::kFloat4: {
191                 int rows = ((int)v.type - (int)SkRuntimeEffect::Uniform::Type::kFloat) + 1;
192                 float* f = reinterpret_cast<float*>(data);
193                 for (int c = 0; c < v.count; ++c, f += rows) {
194                     SkString name = v.isArray() ? SkStringPrintf("%s[%d]", v.name.c_str(), c)
195                                                 : v.name;
196                     ImGui::PushID(c);
197                     ImGui::DragScalarN(name.c_str(), ImGuiDataType_Float, f, rows, 1.0f);
198                     ImGui::PopID();
199                 }
200                 break;
201             }
202             case SkRuntimeEffect::Uniform::Type::kFloat2x2:
203             case SkRuntimeEffect::Uniform::Type::kFloat3x3:
204             case SkRuntimeEffect::Uniform::Type::kFloat4x4: {
205                 int rows = ((int)v.type - (int)SkRuntimeEffect::Uniform::Type::kFloat2x2) + 2;
206                 int cols = rows;
207                 float* f = reinterpret_cast<float*>(data);
208                 for (int e = 0; e < v.count; ++e) {
209                     for (int c = 0; c < cols; ++c, f += rows) {
210                         SkString name = v.isArray()
211                             ? SkStringPrintf("%s[%d][%d]", v.name.c_str(), e, c)
212                             : SkStringPrintf("%s[%d]", v.name.c_str(), c);
213                         ImGui::DragScalarN(name.c_str(), ImGuiDataType_Float, f, rows, 1.0f);
214                     }
215                 }
216                 break;
217             }
218             case SkRuntimeEffect::Uniform::Type::kInt:
219             case SkRuntimeEffect::Uniform::Type::kInt2:
220             case SkRuntimeEffect::Uniform::Type::kInt3:
221             case SkRuntimeEffect::Uniform::Type::kInt4: {
222                 int rows = ((int)v.type - (int)SkRuntimeEffect::Uniform::Type::kInt) + 1;
223                 int* i = reinterpret_cast<int*>(data);
224                 for (int c = 0; c < v.count; ++c, i += rows) {
225                     SkString name = v.isArray() ? SkStringPrintf("%s[%d]", v.name.c_str(), c)
226                                                 : v.name;
227                     ImGui::PushID(c);
228                     ImGui::DragScalarN(name.c_str(), ImGuiDataType_S32, i, rows, 1.0f);
229                     ImGui::PopID();
230                 }
231                 break;
232             }
233         }
234     }
235 
236     for (const auto& c : fEffect->children()) {
237         auto curShader =
238                 std::find_if(fShaders.begin(), fShaders.end(), [tgt = fChildren[c.index]](auto p) {
239                     return p.second == tgt;
240                 });
241         SkASSERT(curShader != fShaders.end());
242 
243         if (ImGui::BeginCombo(c.name.c_str(), curShader->first)) {
244             for (const auto& namedShader : fShaders) {
245                 if (ImGui::Selectable(namedShader.first, curShader->second == namedShader.second)) {
246                     fChildren[c.index] = namedShader.second;
247                 }
248             }
249             ImGui::EndCombo();
250         }
251     }
252 
253     static SkColor4f gPaintColor { 1.0f, 1.0f, 1.0f , 1.0f };
254     ImGui::ColorEdit4("Paint Color", gPaintColor.vec());
255 
256     ImGui::RadioButton("Fill",      &fGeometry, kFill);      ImGui::SameLine();
257     ImGui::RadioButton("Circle",    &fGeometry, kCircle);    ImGui::SameLine();
258     ImGui::RadioButton("RoundRect", &fGeometry, kRoundRect); ImGui::SameLine();
259     ImGui::RadioButton("Capsule",   &fGeometry, kCapsule);   ImGui::SameLine();
260     ImGui::RadioButton("Text",      &fGeometry, kText);
261 
262     ImGui::End();
263 
264     auto inputs = SkData::MakeWithoutCopy(fInputs.get(), fEffect->uniformSize());
265 
266     canvas->save();
267 
268     sk_sp<SkSL::DebugTrace> debugTrace;
269     auto shader = fEffect->makeShader(std::move(inputs), fChildren.data(), fChildren.count());
270     if (writeTrace || writeDump) {
271         SkIPoint traceCoord = {fTraceCoord[0], fTraceCoord[1]};
272         SkRuntimeEffect::TracedShader traced = SkRuntimeEffect::MakeTraced(std::move(shader),
273                                                                            traceCoord);
274         shader = std::move(traced.shader);
275         debugTrace = std::move(traced.debugTrace);
276 
277         // Reduce debug trace delay by clipping to a 4x4 rectangle for this paint, centered on the
278         // pixel to trace. A minor complication is that the canvas might have a transform applied to
279         // it, but we want to clip in device space. This can be worked around by resetting the
280         // canvas matrix temporarily.
281         SkM44 canvasMatrix = canvas->getLocalToDevice();
282         canvas->resetMatrix();
283         auto r = SkRect::MakeXYWH(fTraceCoord[0] - 1, fTraceCoord[1] - 1, 4, 4);
284         canvas->clipRect(r, SkClipOp::kIntersect);
285         canvas->setMatrix(canvasMatrix);
286     }
287     SkPaint p;
288     p.setColor4f(gPaintColor);
289     p.setShader(std::move(shader));
290 
291     switch (fGeometry) {
292         case kFill:
293             canvas->drawPaint(p);
294             break;
295         case kCircle:
296             canvas->drawCircle({ 256, 256 }, 256, p);
297             break;
298         case kRoundRect:
299             canvas->drawRoundRect({ 0, 0, 512, 512 }, 64, 64, p);
300             break;
301         case kCapsule:
302             canvas->drawRoundRect({ 0, 224, 512, 288 }, 32, 32, p);
303             break;
304         case kText: {
305             SkFont font;
306             font.setSize(SkIntToScalar(96));
307             canvas->drawSimpleText("Hello World", strlen("Hello World"), SkTextEncoding::kUTF8, 0,
308                                    256, font, p);
309         } break;
310         default: break;
311     }
312 
313     canvas->restore();
314 
315     if (debugTrace && writeTrace) {
316         SkFILEWStream traceFile("SkVMDebugTrace.json");
317         debugTrace->writeTrace(&traceFile);
318     }
319     if (debugTrace && writeDump) {
320         SkFILEWStream dumpFile("SkVMDebugTrace.dump.txt");
321         debugTrace->dump(&dumpFile);
322     }
323 }
324 
animate(double nanos)325 bool SkSLSlide::animate(double nanos) {
326     fSeconds = static_cast<float>(nanos * 1E-9);
327     return true;
328 }
329