| 1 | /* |
| 2 | * Copyright 2021 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 | #ifndef SkMesh_DEFINED |
| 9 | #define SkMesh_DEFINED |
| 10 | |
| 11 | #include "include/core/SkData.h" |
| 12 | #include "include/core/SkRect.h" |
| 13 | #include "include/core/SkRefCnt.h" |
| 14 | #include "include/core/SkSpan.h" |
| 15 | #include "include/core/SkString.h" |
| 16 | #include "include/effects/SkRuntimeEffect.h" |
| 17 | #include "include/private/base/SkAPI.h" |
| 18 | |
| 19 | #include <cstddef> |
| 20 | #include <cstdint> |
| 21 | #include <memory> |
| 22 | #include <string_view> |
| 23 | #include <tuple> |
| 24 | #include <vector> |
| 25 | |
| 26 | class GrDirectContext; |
| 27 | class SkColorSpace; |
| 28 | enum SkAlphaType : int; |
| 29 | |
| 30 | namespace SkSL { struct Program; } |
| 31 | |
| 32 | /** |
| 33 | * A specification for custom meshes. Specifies the vertex buffer attributes and stride, the |
| 34 | * vertex program that produces a user-defined set of varyings, and a fragment program that ingests |
| 35 | * the interpolated varyings and produces local coordinates for shading and optionally a color. |
| 36 | * |
| 37 | * The varyings must include a float2 named "position". If the passed varyings does not |
| 38 | * contain such a varying then one is implicitly added to the final specification and the SkSL |
| 39 | * Varyings struct described below. It is an error to have a varying named "position" that has a |
| 40 | * type other than float2. |
| 41 | * |
| 42 | * The provided attributes and varyings are used to create Attributes and Varyings structs in SkSL |
| 43 | * that are used by the shaders. Each attribute from the Attribute span becomes a member of the |
| 44 | * SkSL Attributes struct and likewise for the varyings. |
| 45 | * |
| 46 | * The signature of the vertex program must be: |
| 47 | * Varyings main(const Attributes). |
| 48 | * |
| 49 | * The signature of the fragment program must be either: |
| 50 | * float2 main(const Varyings) |
| 51 | * or |
| 52 | * float2 main(const Varyings, out (half4|float4) color) |
| 53 | * |
| 54 | * where the return value is the local coordinates that will be used to access SkShader. If the |
| 55 | * color variant is used, the returned color will be blended with SkPaint's SkShader (or SkPaint |
| 56 | * color in absence of a SkShader) using the SkBlender passed to SkCanvas drawMesh(). To use |
| 57 | * interpolated local space positions as the shader coordinates, equivalent to how SkPaths are |
| 58 | * shaded, return the position field from the Varying struct as the coordinates. |
| 59 | * |
| 60 | * The vertex and fragment programs may both contain uniforms. Uniforms with the same name are |
| 61 | * assumed to be shared between stages. It is an error to specify uniforms in the vertex and |
| 62 | * fragment program with the same name but different types, dimensionality, or layouts. |
| 63 | */ |
| 64 | class SkMeshSpecification : public SkNVRefCnt<SkMeshSpecification> { |
| 65 | public: |
| 66 | /** These values are enforced when creating a specification. */ |
| 67 | static constexpr size_t kMaxStride = 1024; |
| 68 | static constexpr size_t kMaxAttributes = 8; |
| 69 | static constexpr size_t kStrideAlignment = 4; |
| 70 | static constexpr size_t kOffsetAlignment = 4; |
| 71 | static constexpr size_t kMaxVaryings = 6; |
| 72 | |
| 73 | struct Attribute { |
| 74 | enum class Type : uint32_t { // CPU representation Shader Type |
| 75 | kFloat, // float float |
| 76 | kFloat2, // two floats float2 |
| 77 | kFloat3, // three floats float3 |
| 78 | kFloat4, // four floats float4 |
| 79 | kUByte4_unorm, // four bytes half4 |
| 80 | |
| 81 | kLast = kUByte4_unorm |
| 82 | }; |
| 83 | Type type; |
| 84 | size_t offset; |
| 85 | SkString name; |
| 86 | }; |
| 87 | |
| 88 | struct Varying { |
| 89 | enum class Type : uint32_t { |
| 90 | kFloat, // "float" |
| 91 | kFloat2, // "float2" |
| 92 | kFloat3, // "float3" |
| 93 | kFloat4, // "float4" |
| 94 | kHalf, // "half" |
| 95 | kHalf2, // "half2" |
| 96 | kHalf3, // "half3" |
| 97 | kHalf4, // "half4" |
| 98 | |
| 99 | kLast = kHalf4 |
| 100 | }; |
| 101 | Type type; |
| 102 | SkString name; |
| 103 | }; |
| 104 | |
| 105 | using Uniform = SkRuntimeEffect::Uniform; |
| 106 | |
| 107 | ~SkMeshSpecification(); |
| 108 | |
| 109 | struct Result { |
| 110 | sk_sp<SkMeshSpecification> specification; |
| 111 | SkString error; |
| 112 | }; |
| 113 | |
| 114 | /** |
| 115 | * If successful the return is a specification and an empty error string. Otherwise, it is a |
| 116 | * null specification a non-empty error string. |
| 117 | * |
| 118 | * @param attributes The vertex attributes that will be consumed by 'vs'. Attributes need |
| 119 | * not be tightly packed but attribute offsets must be aligned to |
| 120 | * kOffsetAlignment and offset + size may not be greater than |
| 121 | * 'vertexStride'. At least one attribute is required. |
| 122 | * @param vertexStride The offset between successive attribute values. This must be aligned to |
| 123 | * kStrideAlignment. |
| 124 | * @param varyings The varyings that will be written by 'vs' and read by 'fs'. This may |
| 125 | * be empty. |
| 126 | * @param vs The vertex shader code that computes a vertex position and the varyings |
| 127 | * from the attributes. |
| 128 | * @param fs The fragment code that computes a local coordinate and optionally a |
| 129 | * color from the varyings. The local coordinate is used to sample |
| 130 | * SkShader. |
| 131 | * @param cs The colorspace of the color produced by 'fs'. Ignored if 'fs's main() |
| 132 | * function does not have a color out param. |
| 133 | * @param at The alpha type of the color produced by 'fs'. Ignored if 'fs's main() |
| 134 | * function does not have a color out param. Cannot be kUnknown. |
| 135 | */ |
| 136 | static Result Make(SkSpan<const Attribute> attributes, |
| 137 | size_t vertexStride, |
| 138 | SkSpan<const Varying> varyings, |
| 139 | const SkString& vs, |
| 140 | const SkString& fs); |
| 141 | static Result Make(SkSpan<const Attribute> attributes, |
| 142 | size_t vertexStride, |
| 143 | SkSpan<const Varying> varyings, |
| 144 | const SkString& vs, |
| 145 | const SkString& fs, |
| 146 | sk_sp<SkColorSpace> cs); |
| 147 | static Result Make(SkSpan<const Attribute> attributes, |
| 148 | size_t vertexStride, |
| 149 | SkSpan<const Varying> varyings, |
| 150 | const SkString& vs, |
| 151 | const SkString& fs, |
| 152 | sk_sp<SkColorSpace> cs, |
| 153 | SkAlphaType at); |
| 154 | |
| 155 | SkSpan<const Attribute> attributes() const { return SkSpan(fAttributes); } |
| 156 | |
| 157 | /** |
| 158 | * Combined size of all 'uniform' variables. When creating a SkMesh with this specification |
| 159 | * provide an SkData of this size, containing values for all of those variables. Use uniforms() |
| 160 | * to get the offset of each uniform within the SkData. |
| 161 | */ |
| 162 | size_t uniformSize() const; |
| 163 | |
| 164 | /** |
| 165 | * Provides info about individual uniforms including the offset into an SkData where each |
| 166 | * uniform value should be placed. |
| 167 | */ |
| 168 | SkSpan<const Uniform> uniforms() const { return SkSpan(fUniforms); } |
| 169 | |
| 170 | /** Returns pointer to the named uniform variable's description, or nullptr if not found. */ |
| 171 | const Uniform* findUniform(std::string_view name) const; |
| 172 | |
| 173 | /** Returns pointer to the named attribute, or nullptr if not found. */ |
| 174 | const Attribute* findAttribute(std::string_view name) const; |
| 175 | |
| 176 | /** Returns pointer to the named varying, or nullptr if not found. */ |
| 177 | const Varying* findVarying(std::string_view name) const; |
| 178 | |
| 179 | size_t stride() const { return fStride; } |
| 180 | |
| 181 | private: |
| 182 | friend struct SkMeshSpecificationPriv; |
| 183 | |
| 184 | enum class ColorType { |
| 185 | kNone, |
| 186 | kHalf4, |
| 187 | kFloat4, |
| 188 | }; |
| 189 | |
| 190 | static Result MakeFromSourceWithStructs(SkSpan<const Attribute> attributes, |
| 191 | size_t stride, |
| 192 | SkSpan<const Varying> varyings, |
| 193 | const SkString& vs, |
| 194 | const SkString& fs, |
| 195 | sk_sp<SkColorSpace> cs, |
| 196 | SkAlphaType at); |
| 197 | |
| 198 | SkMeshSpecification(SkSpan<const Attribute>, |
| 199 | size_t, |
| 200 | SkSpan<const Varying>, |
| 201 | int passthroughLocalCoordsVaryingIndex, |
| 202 | uint32_t deadVaryingMask, |
| 203 | std::vector<Uniform> uniforms, |
| 204 | std::unique_ptr<const SkSL::Program>, |
| 205 | std::unique_ptr<const SkSL::Program>, |
| 206 | ColorType, |
| 207 | sk_sp<SkColorSpace>, |
| 208 | SkAlphaType); |
| 209 | |
| 210 | SkMeshSpecification(const SkMeshSpecification&) = delete; |
| 211 | SkMeshSpecification(SkMeshSpecification&&) = delete; |
| 212 | |
| 213 | SkMeshSpecification& operator=(const SkMeshSpecification&) = delete; |
| 214 | SkMeshSpecification& operator=(SkMeshSpecification&&) = delete; |
| 215 | |
| 216 | const std::vector<Attribute> fAttributes; |
| 217 | const std::vector<Varying> fVaryings; |
| 218 | const std::vector<Uniform> fUniforms; |
| 219 | const std::unique_ptr<const SkSL::Program> fVS; |
| 220 | const std::unique_ptr<const SkSL::Program> fFS; |
| 221 | const size_t fStride; |
| 222 | uint32_t fHash; |
| 223 | const int fPassthroughLocalCoordsVaryingIndex; |
| 224 | const uint32_t fDeadVaryingMask; |
| 225 | const ColorType fColorType; |
| 226 | const sk_sp<SkColorSpace> fColorSpace; |
| 227 | const SkAlphaType fAlphaType; |
| 228 | }; |
| 229 | |
| 230 | /** |
| 231 | * A vertex buffer, a topology, optionally an index buffer, and a compatible SkMeshSpecification. |
| 232 | * |
| 233 | * The data in the vertex buffer is expected to contain the attributes described by the spec |
| 234 | * for vertexCount vertices beginning at vertexOffset. vertexOffset must be aligned to the |
| 235 | * SkMeshSpecification's vertex stride. The size of the buffer must be at least vertexOffset + |
| 236 | * spec->stride()*vertexCount (even if vertex attributes contains pad at the end of the stride). If |
| 237 | * the specified bounds does not contain all the points output by the spec's vertex program when |
| 238 | * applied to the vertices in the custom mesh then the result is undefined. |
| 239 | * |
| 240 | * MakeIndexed may be used to create an indexed mesh. indexCount indices are read from the index |
| 241 | * buffer at the specified offset which must be aligned to 2. The indices are always unsigned 16bit |
| 242 | * integers. The index count must be at least 3. |
| 243 | * |
| 244 | * If Make() is used the implicit index sequence is 0, 1, 2, 3, ... and vertexCount must be at least |
| 245 | * 3. |
| 246 | * |
| 247 | * Both Make() and MakeIndexed() take a SkData with the uniform values. See |
| 248 | * SkMeshSpecification::uniformSize() and SkMeshSpecification::uniforms() for sizing and packing |
| 249 | * uniforms into the SkData. |
| 250 | */ |
| 251 | class SkMesh { |
| 252 | public: |
| 253 | class IndexBuffer : public SkRefCnt { |
| 254 | public: |
| 255 | virtual size_t size() const = 0; |
| 256 | |
| 257 | /** |
| 258 | * Modifies the data in the IndexBuffer by copying size bytes from data into the buffer |
| 259 | * at offset. Fails if offset + size > this->size() or if either offset or size is not |
| 260 | * aligned to 4 bytes. The GrDirectContext* must match that used to create the buffer. We |
| 261 | * take it as a parameter to emphasize that the context must be used to update the data and |
| 262 | * thus the context must be valid for the current thread. |
| 263 | */ |
| 264 | bool update(GrDirectContext*, const void* data, size_t offset, size_t size); |
| 265 | |
| 266 | private: |
| 267 | virtual bool onUpdate(GrDirectContext*, const void* data, size_t offset, size_t size) = 0; |
| 268 | }; |
| 269 | |
| 270 | class VertexBuffer : public SkRefCnt { |
| 271 | public: |
| 272 | virtual size_t size() const = 0; |
| 273 | |
| 274 | /** |
| 275 | * Modifies the data in the IndexBuffer by copying size bytes from data into the buffer |
| 276 | * at offset. Fails if offset + size > this->size() or if either offset or size is not |
| 277 | * aligned to 4 bytes. The GrDirectContext* must match that used to create the buffer. We |
| 278 | * take it as a parameter to emphasize that the context must be used to update the data and |
| 279 | * thus the context must be valid for the current thread. |
| 280 | */ |
| 281 | bool update(GrDirectContext*, const void* data, size_t offset, size_t size); |
| 282 | |
| 283 | private: |
| 284 | virtual bool onUpdate(GrDirectContext*, const void* data, size_t offset, size_t size) = 0; |
| 285 | }; |
| 286 | |
| 287 | SkMesh(); |
| 288 | ~SkMesh(); |
| 289 | |
| 290 | SkMesh(const SkMesh&); |
| 291 | SkMesh(SkMesh&&); |
| 292 | |
| 293 | SkMesh& operator=(const SkMesh&); |
| 294 | SkMesh& operator=(SkMesh&&); |
| 295 | |
| 296 | enum class Mode { kTriangles, kTriangleStrip }; |
| 297 | |
| 298 | struct Result; |
| 299 | |
| 300 | /** |
| 301 | * Creates a non-indexed SkMesh. The returned SkMesh can be tested for validity using |
| 302 | * SkMesh::isValid(). An invalid mesh simply fails to draws if passed to SkCanvas::drawMesh(). |
| 303 | * If the mesh is invalid the returned string give contain the reason for the failure (e.g. the |
| 304 | * vertex buffer was null or uniform data too small). |
| 305 | */ |
| 306 | static Result Make(sk_sp<SkMeshSpecification>, |
| 307 | Mode, |
| 308 | sk_sp<VertexBuffer>, |
| 309 | size_t vertexCount, |
| 310 | size_t vertexOffset, |
| 311 | sk_sp<const SkData> uniforms, |
| 312 | const SkRect& bounds); |
| 313 | |
| 314 | /** |
| 315 | * Creates an indexed SkMesh. The returned SkMesh can be tested for validity using |
| 316 | * SkMesh::isValid(). A invalid mesh simply fails to draw if passed to SkCanvas::drawMesh(). |
| 317 | * If the mesh is invalid the returned string give contain the reason for the failure (e.g. the |
| 318 | * index buffer was null or uniform data too small). |
| 319 | */ |
| 320 | static Result MakeIndexed(sk_sp<SkMeshSpecification>, |
| 321 | Mode, |
| 322 | sk_sp<VertexBuffer>, |
| 323 | size_t vertexCount, |
| 324 | size_t vertexOffset, |
| 325 | sk_sp<IndexBuffer>, |
| 326 | size_t indexCount, |
| 327 | size_t indexOffset, |
| 328 | sk_sp<const SkData> uniforms, |
| 329 | const SkRect& bounds); |
| 330 | |
| 331 | sk_sp<SkMeshSpecification> refSpec() const { return fSpec; } |
| 332 | SkMeshSpecification* spec() const { return fSpec.get(); } |
| 333 | |
| 334 | Mode mode() const { return fMode; } |
| 335 | |
| 336 | sk_sp<VertexBuffer> refVertexBuffer() const { return fVB; } |
| 337 | VertexBuffer* vertexBuffer() const { return fVB.get(); } |
| 338 | |
| 339 | size_t vertexOffset() const { return fVOffset; } |
| 340 | size_t vertexCount() const { return fVCount; } |
| 341 | |
| 342 | sk_sp<IndexBuffer> refIndexBuffer() const { return fIB; } |
| 343 | IndexBuffer* indexBuffer() const { return fIB.get(); } |
| 344 | |
| 345 | size_t indexOffset() const { return fIOffset; } |
| 346 | size_t indexCount() const { return fICount; } |
| 347 | |
| 348 | sk_sp<const SkData> refUniforms() const { return fUniforms; } |
| 349 | const SkData* uniforms() const { return fUniforms.get(); } |
| 350 | |
| 351 | SkRect bounds() const { return fBounds; } |
| 352 | |
| 353 | bool isValid() const; |
| 354 | |
| 355 | private: |
| 356 | std::tuple<bool, SkString> validate() const; |
| 357 | |
| 358 | sk_sp<SkMeshSpecification> fSpec; |
| 359 | |
| 360 | sk_sp<VertexBuffer> fVB; |
| 361 | sk_sp<IndexBuffer> fIB; |
| 362 | |
| 363 | sk_sp<const SkData> fUniforms; |
| 364 | |
| 365 | size_t fVOffset = 0; // Must be a multiple of spec->stride() |
| 366 | size_t fVCount = 0; |
| 367 | |
| 368 | size_t fIOffset = 0; // Must be a multiple of sizeof(uint16_t) |
| 369 | size_t fICount = 0; |
| 370 | |
| 371 | Mode fMode = Mode::kTriangles; |
| 372 | |
| 373 | SkRect fBounds = SkRect::MakeEmpty(); |
| 374 | }; |
| 375 | |
| 376 | struct SkMesh::Result { SkMesh mesh; SkString error; }; |
| 377 | |
| 378 | namespace SkMeshes { |
| 379 | /** |
| 380 | * Makes a CPU-backed index buffer to be used with SkMeshes. |
| 381 | * |
| 382 | * @param data The data used to populate the buffer, or nullptr to create a zero- |
| 383 | * initialized buffer. |
| 384 | * @param size Both the size of the data in 'data' and the size of the resulting |
| 385 | * buffer. |
| 386 | */ |
| 387 | SK_API sk_sp<SkMesh::IndexBuffer> MakeIndexBuffer(const void* data, size_t size); |
| 388 | |
| 389 | /** |
| 390 | * Makes a copy of an index buffer. The copy will be CPU-backed. |
| 391 | */ |
| 392 | SK_API sk_sp<SkMesh::IndexBuffer> CopyIndexBuffer(sk_sp<SkMesh::IndexBuffer>); |
| 393 | |
| 394 | /** |
| 395 | * Makes a CPU-backed vertex buffer to be used with SkMeshes. |
| 396 | * |
| 397 | * @param data The data used to populate the buffer, or nullptr to create a zero- |
| 398 | * initialized buffer. |
| 399 | * @param size Both the size of the data in 'data' and the size of the resulting |
| 400 | * buffer. |
| 401 | */ |
| 402 | SK_API sk_sp<SkMesh::VertexBuffer> MakeVertexBuffer(const void*, size_t size); |
| 403 | |
| 404 | /** |
| 405 | * Makes a copy of a vertex buffer. The copy will be CPU-backed. |
| 406 | */ |
| 407 | SK_API sk_sp<SkMesh::VertexBuffer> CopyVertexBuffer(sk_sp<SkMesh::VertexBuffer>); |
| 408 | } // namespace SkMeshes |
| 409 | |
| 410 | #endif |
| 411 | |