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