blob: e09b6eb954f70ce57e12ce94b5102055931b26cc [file] [log] [blame]
package scrap
import (
"bytes"
"io"
"strings"
"go.skia.org/infra/go/skerr"
)
// extractBodyUniforms will parse the SkSL code in a scrap and extract definitions
// for each uniform defined within.
func extractBodyUniforms(body ScrapBody) ([]uniformValue, error) {
var uniforms []uniformValue
for _, line := range strings.Split(body.Body, "\n") {
if m := uniformDefinitionRegex.FindStringSubmatch(line); m != nil {
uniforms = append(uniforms, uniformValue{
Name: m[2],
Type: m[1],
})
}
}
return uniforms, nil
}
// writeShaderProgramAsCPPStringConstant writes the shader code into a C++
// string constant.
func writeShaderProgramAsCPPStringConstant(w io.StringWriter, node scrapNode) {
mustWriteStringf(w, " constexpr char prog%s[] = R\"(\n", node.Name)
mustWriteStringf(w, indentMultilineString(skslDefaultInputs, 8))
mustWriteStringf(w, "\n")
writeShaderInputDefinitions(w, node, 8)
mustWriteStringf(w, "\n")
mustWriteStringf(w, indentMultilineString(node.Scrap.Body, 8))
mustWriteStringf(w, "\n )\";")
}
// writeCustomUniformSetup will write the Skia C++ code to the supplied
// StringWriter to initialize any custom uniform values defined in the
// scrap.
func writeCustomUniformSetup(w io.StringWriter, node scrapNode) error {
if node.Scrap.Type != SKSL || node.Scrap.SKSLMetaData == nil || len(node.Scrap.SKSLMetaData.Uniforms) == 0 {
return nil
}
uniforms, err := extractBodyUniforms(node.Scrap)
if err != nil {
return skerr.Wrap(err)
}
requiredFloatCount := 0
for _, u := range uniforms {
num, err := u.numFloats()
if err != nil {
return skerr.Wrap(err)
}
requiredFloatCount += num
}
if requiredFloatCount != len(node.Scrap.SKSLMetaData.Uniforms) {
return skerr.Fmt("user inputs size mismatch: %d != %d.",
requiredFloatCount, len(node.Scrap.SKSLMetaData.Uniforms))
}
i := 0
mustWriteStringf(w, " // Inputs supplied by user:\n")
for _, u := range uniforms {
s, err := u.getCppDefinitionString(node.Scrap.SKSLMetaData.Uniforms[i:], node.Name)
if err != nil {
return skerr.Fmt("cannot get C++ definition for %s (%s).", u.Name, u.Type)
}
mustWriteStringf(w, " %s\n", s)
num, _ := u.numFloats()
i += num
}
return nil
}
func writeShaderUniformSetup(w io.StringWriter, node scrapNode) error {
if node.Scrap.Type != SKSL || node.Scrap.SKSLMetaData == nil {
return nil
}
if len(node.Scrap.SKSLMetaData.Children) != len(node.Children) {
// This is a program error. They should be the same size and order.
return skerr.Fmt("Child count mismatch")
}
for i := 0; i < len(node.Children); i++ {
mustWriteStringf(w, " builder%s.child(\"%s\") = shader%s;\n",
node.Name,
node.Scrap.SKSLMetaData.Children[i].UniformName,
node.Children[i].Name,
)
}
return nil
}
func writeShaderCreationCPP(w io.StringWriter, node scrapNode) error {
mustWriteStringf(w, " auto [effect%s, err%s] = SkRuntimeEffect::MakeForShader(SkString(prog%s));\n", node.Name, node.Name, node.Name)
mustWriteStringf(w, " if (!effect%s) {\n", node.Name)
mustWriteStringf(w, " SkDebugf(\"Cannot create effect%s\");\n", node.Name)
mustWriteStringf(w, " return;\n")
mustWriteStringf(w, " }\n")
mustWriteStringf(w, " SkRuntimeShaderBuilder builder%s(effect%s);\n", node.Name, node.Name)
mustWriteStringf(w, " builder%s.uniform(\"iResolution\") = viewportResolution;\n", node.Name)
mustWriteStringf(w, " builder%s.uniform(\"iTime\") = playbackTime;\n", node.Name)
mustWriteStringf(w, " builder%s.uniform(\"iMouse\") = mousePos;\n", node.Name)
mustWriteStringf(w, " builder%s.uniform(\"iImageResolution\") =\n", node.Name)
mustWriteStringf(w, " SkV3{static_cast<float>(image->width()),\n")
mustWriteStringf(w, " static_cast<float>(image->height()), 1.0f};\n")
mustWriteStringf(w, " builder%s.child(\"iImage1\") = image->makeShader(shaderOptions);\n", node.Name)
if err := writeCustomUniformSetup(w, node); err != nil {
return skerr.Wrap(err)
}
if err := writeShaderUniformSetup(w, node); err != nil {
return skerr.Wrap(err)
}
mustWriteStringf(w, " sk_sp<SkShader> shader%s = builder%s.makeShader();\n", node.Name, node.Name)
mustWriteStringf(w, " if (!shader%s) {\n", node.Name)
mustWriteStringf(w, " SkDebugf(\"Cannot create shader%s\");\n", node.Name)
mustWriteStringf(w, " return;\n")
mustWriteStringf(w, " }\n")
return nil
}
// writeCreateShadersCPP writes C++ code need to create the
// runtime effect for the scrap |node| and all child nodes to |w|.
func writeCreateShadersCPP(w io.StringWriter, node scrapNode) error {
for _, child := range node.Children {
mustWriteStringf(w, "\n")
if err := writeCreateShadersCPP(w, child); err != nil {
return skerr.Wrap(err)
}
}
mustWriteStringf(w, "\n")
if node.Name != "" {
mustWriteStringf(w, " // Shader %q:\n", node.Name)
}
writeShaderProgramAsCPPStringConstant(w, node)
mustWriteStringf(w, "\n")
if err := writeShaderCreationCPP(w, node); err != nil {
return skerr.Wrap(err)
}
return nil
}
// createShadersCPP is the template.Template callback to write C++ code to create
// all shaders for the given |root| node and all child nodes.
func createShadersCPP(root scrapNode) (string, error) {
var b bytes.Buffer
if err := writeCreateShadersCPP(&b, root); err != nil {
return "", skerr.Wrap(err)
}
return b.String(), nil
}
const skslCpp = `void draw(SkCanvas *canvas) {
constexpr SkV4 mousePos = SkV4{0.0f, 0.0f, 0.0f, 0.0f};
constexpr SkV3 viewportResolution = SkV3{256, 256, 1.0f};
const SkSamplingOptions shaderOptions(SkFilterMode::kLinear);
const float playbackTime = duration != 0.0 ? frame * duration : 0.0;
{{ createShadersCPP . }}
canvas->clear(SK_ColorBLACK);
// Fill the surface with |shader{{ .Name }}|:
SkPaint p;
p.setShader(shader{{ .Name }});
canvas->drawPaint(p);
}`