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