| /* | |
| * Copyright 2025 Google LLC | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "src/gpu/graphite/precompile/SerializationUtils.h" | |
| #include "include/core/SkFourByteTag.h" | |
| #include "include/core/SkStream.h" | |
| #include "src/base/SkAutoMalloc.h" | |
| #include "src/gpu/SwizzlePriv.h" | |
| #include "src/gpu/graphite/Caps.h" | |
| #include "src/gpu/graphite/GraphicsPipelineDesc.h" | |
| #include "src/gpu/graphite/PaintParamsKey.h" | |
| #include "src/gpu/graphite/RenderPassDesc.h" | |
| #include "src/gpu/graphite/Renderer.h" | |
| #include "src/gpu/graphite/ShaderCodeDictionary.h" | |
| #include "src/gpu/graphite/TextureInfoPriv.h" | |
| namespace skgpu::graphite { | |
| // This is the main control to version the serialized Pipelines (c.f. stream_is_blob) | |
| static const int kCurrent_Version = 1; | |
| namespace { | |
| static const char kMagic[] = { 's', 'k', 'i', 'a', 'p', 'i', 'p', 'e' }; | |
| [[nodiscard]] bool stream_is_pipeline(SkStream* stream) { | |
| char magic[8]; | |
| static_assert(sizeof(kMagic) == sizeof(magic), ""); | |
| if (stream->read(magic, sizeof(kMagic)) != sizeof(kMagic)) { | |
| return false; | |
| } | |
| if (0 != memcmp(magic, kMagic, sizeof(kMagic))) { | |
| return false; | |
| } | |
| uint32_t version; | |
| if (!stream->readU32(&version)) { | |
| return false; | |
| } | |
| if (version != kCurrent_Version) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| [[nodiscard]] bool serialize_graphics_pipeline_desc(ShaderCodeDictionary* shaderCodeDictionary, | |
| SkWStream* stream, | |
| const GraphicsPipelineDesc& pipelineDesc) { | |
| PaintParamsKey key = shaderCodeDictionary->lookup(pipelineDesc.paintParamsID()); | |
| if (!stream->write32(static_cast<uint32_t>(pipelineDesc.renderStepID()))) { | |
| return false; | |
| } | |
| if (!key.isValid()) { | |
| if (!stream->write32(0)) { | |
| return false; | |
| } | |
| // Not all GraphicsPipeline have a valid PaintParamsKey | |
| return true; | |
| } | |
| const SkSpan<const uint32_t> keySpan = key.data(); | |
| if (!key.isSerializable(shaderCodeDictionary)) { | |
| return false; | |
| } | |
| if (!stream->write32(SkToU32(keySpan.size()))) { | |
| return false; | |
| } | |
| if (!stream->write(keySpan.data(), 4 * keySpan.size())) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| [[nodiscard]] bool deserialize_graphics_pipeline_desc(ShaderCodeDictionary* shaderCodeDictionary, | |
| SkStream* stream, | |
| GraphicsPipelineDesc* pipelineDesc) { | |
| uint32_t tmp; | |
| if (!stream->readU32(&tmp)) { | |
| return false; | |
| } | |
| if (tmp >= RenderStep::kNumRenderSteps) { | |
| return false; | |
| } | |
| RenderStep::RenderStepID renderStepID = static_cast<RenderStep::RenderStepID>(tmp); | |
| if (!stream->readU32(&tmp)) { | |
| return false; | |
| } | |
| UniquePaintParamsID paintParamsID = UniquePaintParamsID::Invalid(); | |
| if (tmp) { | |
| SkAutoMalloc storage(4 * tmp); | |
| if (stream->read(storage.get(), 4 * tmp) != 4 * tmp) { | |
| return false; | |
| } | |
| PaintParamsKey ppk = PaintParamsKey(SkSpan<uint32_t>((uint32_t*) storage.get(), tmp)); | |
| if (!ppk.isSerializable(shaderCodeDictionary)) { | |
| return false; | |
| } | |
| paintParamsID = shaderCodeDictionary->findOrCreate(ppk); | |
| } | |
| *pipelineDesc = GraphicsPipelineDesc(renderStepID, paintParamsID); | |
| return true; | |
| } | |
| [[nodiscard]] bool serialize_attachment_desc(SkWStream* stream, | |
| const AttachmentDesc& attachmentDesc) { | |
| uint32_t tag = attachmentDesc.fFormat == TextureFormat::kUnsupported | |
| ? SkSetFourByteTag(static_cast<uint8_t>(TextureFormat::kUnsupported), 0, 0, 0) | |
| : SkSetFourByteTag(static_cast<uint8_t>(attachmentDesc.fFormat), | |
| static_cast<uint8_t>(attachmentDesc.fLoadOp), | |
| static_cast<uint8_t>(attachmentDesc.fStoreOp), | |
| attachmentDesc.fSampleCount); | |
| return stream->write32(tag); | |
| } | |
| [[nodiscard]] bool deserialize_attachment_desc(SkStream* stream, | |
| AttachmentDesc* attachmentDesc) { | |
| uint32_t tag; | |
| if (!stream->readU32(&tag)) { | |
| return false; | |
| } | |
| uint8_t format = static_cast<uint8_t>((tag >> 24) & 0xFF); | |
| uint8_t loadOp = static_cast<uint8_t>((tag >> 16) & 0xFF); | |
| uint8_t storeOp = static_cast<uint8_t>((tag >> 8) & 0xFF); | |
| uint8_t sampleCount = static_cast<uint8_t>((tag >> 0) & 0xFF); | |
| if (format >= kTextureFormatCount) { | |
| return false; | |
| } | |
| if (loadOp >= kLoadOpCount) { | |
| return false; | |
| } | |
| if (storeOp >= kStoreOpCount) { | |
| return false; | |
| } | |
| *attachmentDesc = {static_cast<TextureFormat>(format), | |
| static_cast<LoadOp>(loadOp), | |
| static_cast<StoreOp>(storeOp), | |
| sampleCount}; | |
| return true; | |
| } | |
| [[nodiscard]] bool serialize_render_pass_desc(SkWStream* stream, | |
| const RenderPassDesc& renderPassDesc) { | |
| if (!serialize_attachment_desc(stream, renderPassDesc.fColorAttachment)) { | |
| return false; | |
| } | |
| if (!serialize_attachment_desc(stream, renderPassDesc.fColorResolveAttachment)) { | |
| return false; | |
| } | |
| if (!serialize_attachment_desc(stream, renderPassDesc.fDepthStencilAttachment)) { | |
| return false; | |
| } | |
| if (!stream->write16(renderPassDesc.fWriteSwizzle.asKey())) { | |
| return false; | |
| } | |
| if (!stream->write8(renderPassDesc.fSampleCount)) { | |
| return false; | |
| } | |
| // Omit clear values for the various attachments as they do not effect structure. | |
| // Omit fDstReadStrategy from the serialization because it is not a part of RenderPassDesc | |
| // keys and does not impact pipeline creation. When deserializing, the strategy can be | |
| // obtained via caps->getDstReadStrategy(). | |
| return true; | |
| } | |
| [[nodiscard]] bool deserialize_render_pass_desc(const Caps* caps, | |
| SkStream* stream, | |
| RenderPassDesc* renderPassDesc) { | |
| if (!deserialize_attachment_desc(stream, &renderPassDesc->fColorAttachment)) { | |
| return false; | |
| } | |
| if (!deserialize_attachment_desc(stream, &renderPassDesc->fColorResolveAttachment)) { | |
| return false; | |
| } | |
| if (!deserialize_attachment_desc(stream, &renderPassDesc->fDepthStencilAttachment)) { | |
| return false; | |
| } | |
| uint16_t swizzle; | |
| if (!stream->readU16(&swizzle)) { | |
| return false; | |
| } | |
| renderPassDesc->fWriteSwizzle = SwizzleCtorAccessor::Make(swizzle); | |
| if (!stream->readU8(&renderPassDesc->fSampleCount)) { | |
| return false; | |
| } | |
| // RenderPassDesc dst read strategy is not serialized as it is not something we key on and does | |
| // not impact pipeline creation. When deserializing, simply query Caps again for a | |
| // DstReadStrategy. Leave clear color/depth/stencil as their default values. | |
| renderPassDesc->fDstReadStrategy = caps->getDstReadStrategy(); | |
| return true; | |
| } | |
| #define SK_BLOB_END_TAG SkSetFourByteTag('e', 'n', 'd', ' ') | |
| bool SerializePipelineDesc(ShaderCodeDictionary* shaderCodeDictionary, | |
| SkWStream* stream, | |
| const GraphicsPipelineDesc& pipelineDesc, | |
| const RenderPassDesc& renderPassDesc) { | |
| stream->write(kMagic, sizeof(kMagic)); | |
| stream->write32(kCurrent_Version); | |
| if (!serialize_graphics_pipeline_desc(shaderCodeDictionary, stream, pipelineDesc)) { | |
| return false; | |
| } | |
| if (!serialize_render_pass_desc(stream, renderPassDesc)) { | |
| return false; | |
| } | |
| stream->write32(SK_BLOB_END_TAG); | |
| return true; | |
| } | |
| bool DeserializePipelineDesc(const Caps* caps, | |
| ShaderCodeDictionary* shaderCodeDictionary, | |
| SkStream* stream, | |
| GraphicsPipelineDesc* pipelineDesc, | |
| RenderPassDesc* renderPassDesc) { | |
| SkASSERT(stream); | |
| if (!stream_is_pipeline(stream)) { | |
| return false; | |
| } | |
| if (!deserialize_graphics_pipeline_desc(shaderCodeDictionary, stream, pipelineDesc)) { | |
| return false; | |
| } | |
| if (!deserialize_render_pass_desc(caps, stream, renderPassDesc)) { | |
| return false; | |
| } | |
| uint32_t tag; | |
| if (!stream->readU32(&tag)) { | |
| return false; | |
| } | |
| if (tag != SK_BLOB_END_TAG) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| } // anonymous namespace | |
| sk_sp<SkData> PipelineDescToData(ShaderCodeDictionary* shaderCodeDictionary, | |
| const GraphicsPipelineDesc& pipelineDesc, | |
| const RenderPassDesc& renderPassDesc) { | |
| SkDynamicMemoryWStream stream; | |
| if (!SerializePipelineDesc(shaderCodeDictionary, | |
| &stream, | |
| pipelineDesc, renderPassDesc)) { | |
| return nullptr; | |
| } | |
| return stream.detachAsData(); | |
| } | |
| bool DataToPipelineDesc(const Caps* caps, | |
| ShaderCodeDictionary* shaderCodeDictionary, | |
| const SkData* data, | |
| GraphicsPipelineDesc* pipelineDesc, | |
| RenderPassDesc* renderPassDesc) { | |
| if (!data) { | |
| return false; | |
| } | |
| SkMemoryStream stream(data->data(), data->size()); | |
| if (!DeserializePipelineDesc(caps, | |
| shaderCodeDictionary, | |
| &stream, | |
| pipelineDesc, | |
| renderPassDesc)) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| #if defined(GPU_TEST_UTILS) | |
| void DumpPipelineDesc(const char* label, | |
| ShaderCodeDictionary* shaderCodeDictionary, | |
| const GraphicsPipelineDesc& pipelineDesc, | |
| const RenderPassDesc& renderPassDesc) { | |
| SkString pipelineStr = pipelineDesc.toString(shaderCodeDictionary); | |
| SkString renderPassStr = renderPassDesc.toPipelineLabel(); | |
| SkDebugf("%s: %s - %s\n", label, pipelineStr.c_str(), renderPassStr.c_str()); | |
| } | |
| bool ComparePipelineDescs(const GraphicsPipelineDesc& a1, const RenderPassDesc& b1, | |
| const GraphicsPipelineDesc& a2, const RenderPassDesc& b2) { | |
| return (a1 == a2) && (b1 == b2); | |
| } | |
| #endif | |
| } // namespace skgpu::graphite |