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