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