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