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