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