|  | /* | 
|  | * 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 "tests/Test.h" | 
|  |  | 
|  | #include "include/core/SkBitmap.h" | 
|  | #include "include/gpu/graphite/Context.h" | 
|  | #include "include/gpu/graphite/Recorder.h" | 
|  | #include "include/gpu/graphite/Recording.h" | 
|  | #include "src/gpu/graphite/Buffer.h" | 
|  | #include "src/gpu/graphite/Caps.h" | 
|  | #include "src/gpu/graphite/ContextPriv.h" | 
|  | #include "src/gpu/graphite/DrawPass.h" | 
|  | #include "src/gpu/graphite/DrawWriter.h" | 
|  | #include "src/gpu/graphite/RecorderOptionsPriv.h" | 
|  | #include "src/gpu/graphite/RecorderPriv.h" | 
|  | #include "src/gpu/graphite/RecordingPriv.h" | 
|  | #include "src/gpu/graphite/ResourceProvider.h" | 
|  | #include "src/gpu/graphite/task/CopyTask.h" | 
|  | #include "src/gpu/graphite/task/SynchronizeToCpuTask.h" | 
|  | #include "src/gpu/graphite/task/UploadTask.h" | 
|  |  | 
|  | #include "tools/graphite/GraphiteTestContext.h" | 
|  |  | 
|  | using namespace skgpu::graphite; | 
|  | using namespace skiatest::graphite; | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | std::unique_ptr<Recording> submit_recording(Context* context, | 
|  | GraphiteTestContext* testContext, | 
|  | Recorder* recorder) { | 
|  | std::unique_ptr<Recording> recording = recorder->snap(); | 
|  | if (!recording) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | InsertRecordingInfo insertInfo; | 
|  | insertInfo.fRecording = recording.get(); | 
|  | context->insertRecording(insertInfo); | 
|  | testContext->syncedSubmit(context); | 
|  |  | 
|  | return recording; | 
|  | } | 
|  |  | 
|  | void* map_buffer(Context* context, | 
|  | skiatest::graphite::GraphiteTestContext* testContext, | 
|  | Buffer* bufferToMap, | 
|  | size_t readOffset) { | 
|  | SkASSERT(bufferToMap); | 
|  | if (context->priv().caps()->bufferMapsAreAsync()) { | 
|  | bufferToMap->asyncMap(); | 
|  | while (!bufferToMap->isMapped()) { | 
|  | testContext->tick(); | 
|  | } | 
|  | } | 
|  | std::byte* mappedBytes = static_cast<std::byte*>(bufferToMap->map()); | 
|  | SkASSERT(mappedBytes); | 
|  |  | 
|  | return mappedBytes + readOffset; | 
|  | } | 
|  |  | 
|  | sk_sp<Buffer> get_readback_buffer(Recorder* recorder, size_t copyBufferSize) { | 
|  | return recorder->priv().resourceProvider()->findOrCreateNonShareableBuffer( | 
|  | copyBufferSize, | 
|  | BufferType::kXferGpuToCpu, | 
|  | AccessPattern::kHostVisible, | 
|  | "VerticesPaddingTest_Copy"); | 
|  | } | 
|  |  | 
|  | template<typename T> | 
|  | bool map_and_readback_buffer_sync(Context* context, | 
|  | GraphiteTestContext* testContext, | 
|  | Buffer* sourceBuffer, | 
|  | skia_private::TArray<T>& readbackDst, | 
|  | uint32_t readbackSize) { | 
|  | if (sourceBuffer->isMapped()) { | 
|  | sourceBuffer->unmap(); | 
|  | } | 
|  | T* read = static_cast<T*>(map_buffer(context, testContext, sourceBuffer, 0)); | 
|  | if (!read) { | 
|  | return false; | 
|  | } | 
|  | readbackDst = skia_private::TArray<T>(read, readbackSize); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | template<typename T> | 
|  | bool copy_and_readback_buffer_sync(Context* context, | 
|  | GraphiteTestContext* testContext, | 
|  | Recorder* recorder, | 
|  | Buffer* sourceBuffer, | 
|  | sk_sp<Buffer> copyBuffer, | 
|  | skia_private::TArray<T>& readbackDst, | 
|  | uint32_t readbackSize) { | 
|  | SkASSERT(!copyBuffer->isMapped() && readbackSize <= copyBuffer->size()); | 
|  | recorder->priv().add(CopyBufferToBufferTask::Make(sourceBuffer, | 
|  | /*srcOffset=*/0, | 
|  | copyBuffer, | 
|  | /*dstOffset=*/0, | 
|  | readbackSize)); | 
|  | if (!submit_recording(context, testContext, recorder)) { | 
|  | return false; | 
|  | } | 
|  | T* read = static_cast<T*>(map_buffer(context, testContext, copyBuffer.get(), 0)); | 
|  | if (!read) { | 
|  | return false; | 
|  | } | 
|  | readbackDst = skia_private::TArray<T>(read, readbackSize); | 
|  | copyBuffer->unmap(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool is_dawn_or_vulkan_context_type(skiatest::GpuContextType contextType) { | 
|  | return skiatest::IsDawnContextType(contextType) || skiatest::IsVulkanContextType(contextType); | 
|  | } | 
|  |  | 
|  | } // namespace | 
|  |  | 
|  | #define DEF_GRAPHITE_TEST_FOR_DAWN_AND_VULKAN_CONTEXTS(            \ | 
|  | testName, reporterObj, graphiteContext, testContextObj)    \ | 
|  | DEF_GRAPHITE_TEST_FOR_CONTEXTS(testName,                       \ | 
|  | is_dawn_or_vulkan_context_type, \ | 
|  | reporterObj,                    \ | 
|  | graphiteContext,                \ | 
|  | testContextObj,                 \ | 
|  | CtsEnforcement::kNever) | 
|  |  | 
|  | DEF_GRAPHITE_TEST_FOR_DAWN_AND_VULKAN_CONTEXTS(StaticVerticesPaddingTest, | 
|  | reporter, | 
|  | context, | 
|  | testContext) { | 
|  | std::unique_ptr<Recorder> recorder = context->makeRecorder(); | 
|  | auto sharedContext = recorder->priv().sharedContext(); | 
|  |  | 
|  | auto staticVertexCopyRanges = sharedContext->globalCache()->getStaticVertexCopyRanges(); | 
|  | if (staticVertexCopyRanges.empty()) { | 
|  | ERRORF(reporter, "Did not get the static vertex information (copy ranges)"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | uint32_t vertexInfoElementCount = static_cast<uint32_t>(staticVertexCopyRanges.size()); | 
|  | if (vertexInfoElementCount & 3) { | 
|  | ERRORF(reporter, "Initial static vert information was not correctly populated: " | 
|  | "Element count was %u, expected multiple of 4.", vertexInfoElementCount); | 
|  | return; | 
|  | } | 
|  |  | 
|  | auto staticGpuVertexBuffer = sharedContext->globalCache()->getStaticVertexBuffer(); | 
|  | if (!staticGpuVertexBuffer){ | 
|  | ERRORF(reporter, "Did not get a valid static vertex buffer."); | 
|  | return; | 
|  | } | 
|  |  | 
|  | submit_recording(context, testContext, recorder.get()); | 
|  |  | 
|  | // Readback exact size of static data | 
|  | const uint32_t readbackSize = staticGpuVertexBuffer->size(); | 
|  | auto copyBuff = get_readback_buffer(recorder.get(), readbackSize); | 
|  | skia_private::TArray<uint8_t> readbackData; | 
|  | if (!copy_and_readback_buffer_sync(context, | 
|  | testContext, | 
|  | recorder.get(), | 
|  | staticGpuVertexBuffer.get(), | 
|  | copyBuff, | 
|  | readbackData, | 
|  | readbackSize)) { | 
|  | ERRORF(reporter, "Bad readback, exiting early."); | 
|  | return; | 
|  | } | 
|  |  | 
|  | const uint32_t numRegionsToCheck = vertexInfoElementCount / 4; | 
|  | for (uint32_t i = 0; i < numRegionsToCheck; ++i) { | 
|  | auto cr = staticVertexCopyRanges[i]; | 
|  | const uint32_t regionEndOffset = cr.fOffset + cr.fSize; | 
|  | if (cr.fOffset % cr.fRequiredAlignment) { | 
|  | ERRORF(reporter, | 
|  | "Offset was not safely aligned to a count 4 stride: " | 
|  | "Count four stride: %u Offset: %u Expected: %u\n", | 
|  | cr.fRequiredAlignment, | 
|  | cr.fOffset, | 
|  | SkAlignNonPow2(cr.fOffset, cr.fRequiredAlignment)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (regionEndOffset % cr.fRequiredAlignment) { | 
|  | ERRORF(reporter, | 
|  | "End was not safely aligned to a count 4 stride: " | 
|  | "Count four stride: %u End: %u Expected: %u\n", | 
|  | cr.fRequiredAlignment, | 
|  | regionEndOffset, | 
|  | SkAlignNonPow2(regionEndOffset, cr.fRequiredAlignment)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | const uint32_t paddingStart = cr.fOffset + cr.fUnalignedSize; | 
|  | for (uint32_t byteIndex = paddingStart; byteIndex < regionEndOffset; ++byteIndex) { | 
|  | if (readbackData[byteIndex]) { | 
|  | ERRORF(reporter, | 
|  | "Buffer was not safely padded at byte: " | 
|  | "%u Unaligned End of Buffer: %u Aligned End of Buffer: %u\n", | 
|  | byteIndex, | 
|  | paddingStart, | 
|  | regionEndOffset); | 
|  | return; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | DEF_GRAPHITE_TEST_FOR_DAWN_AND_VULKAN_CONTEXTS(DynamicVerticesPaddingTest, | 
|  | reporter, | 
|  | context, | 
|  | testContext) { | 
|  | constexpr uint32_t kStride = 4; | 
|  | constexpr uint32_t kVertBuffSize = 32; // I.e. blocks of 8 verts (vertBuffSize / stride = 8) | 
|  | constexpr uint32_t kReadbackSize = kVertBuffSize / kStride; | 
|  |  | 
|  | auto buffOpts = DrawBufferManager::Options(); | 
|  | buffOpts.fVertexBufferMinSize = kVertBuffSize; | 
|  | buffOpts.fVertexBufferMaxSize = kVertBuffSize; | 
|  | buffOpts.fUseExactBuffSizes = true; | 
|  | RecorderOptionsPriv recOptsPriv; | 
|  | recOptsPriv.fDbmOptions = std::optional(buffOpts); | 
|  | RecorderOptions recOpts; | 
|  | recOpts.fRecorderOptionsPriv = &recOptsPriv; | 
|  | std::unique_ptr<Recorder> recorder = context->makeRecorder(recOpts); | 
|  |  | 
|  | // b/422605960 Whatever the Quadro P400 is copying is empty, suggesting that the transfer buffer | 
|  | // is being reused and cleared before the data can be read from it. Temporarily disable this | 
|  | // test on all devices that use transfer buffers (implied by unmappable). | 
|  | const bool mappableDrawBuffers = recorder->priv().caps()->drawBufferCanBeMapped(); | 
|  | if (!mappableDrawBuffers) { | 
|  | INFOF(reporter, "Draw buffers not mappable test skipped."); | 
|  | return; | 
|  | } | 
|  |  | 
|  | DrawBufferManager* bufferMgr = recorder->priv().drawBufferManager(); | 
|  | if (bufferMgr->hasMappingFailed()) { | 
|  | ERRORF(reporter, "Failed to get buffer manager"); | 
|  | return; | 
|  | } | 
|  | DrawPassCommands::List commandList; | 
|  | std::unique_ptr<DrawWriter> dw = std::make_unique<DrawWriter>(&commandList, bufferMgr); | 
|  |  | 
|  | auto vertsNewPipeline = [&]() { | 
|  | dw->newPipelineState(/*type=*/{}, kStride, kStride, RenderStateFlags::kAppendVertices, | 
|  | BarrierType::kNone); | 
|  | return; | 
|  | }; | 
|  |  | 
|  | constexpr uint32_t V = UINT32_MAX; | 
|  | auto vertAppend = [&](uint32_t count) -> BindBufferInfo { | 
|  | DrawWriter::Vertices vdw{*dw}; | 
|  | auto a = vdw.append(count); | 
|  | for (uint32_t i = 0; i < count; ++i) { | 
|  | a << V; | 
|  | } | 
|  | return dw->getLastAppendedBuffer(); | 
|  | }; | 
|  |  | 
|  | BindBufferInfo lastBBI; | 
|  | skia_private::TArray<BindBufferInfo> bindBuffers(4); | 
|  | auto checkAndAdvance = [&](const BindBufferInfo& currentBBI, bool expectSameBuffer) { | 
|  | bool isSameBuffer = lastBBI.fBuffer == currentBBI.fBuffer; | 
|  | if (expectSameBuffer != isSameBuffer) { | 
|  | ERRORF(reporter, "Error: Expected %s buffer.\n", expectSameBuffer ? "same" : "new"); | 
|  | return true; | 
|  | } else if (!isSameBuffer) { | 
|  | bindBuffers.push_back(currentBBI); | 
|  | } | 
|  | lastBBI = currentBBI; | 
|  | return false; | 
|  | }; | 
|  |  | 
|  | vertsNewPipeline(); | 
|  | lastBBI = vertAppend(1); | 
|  | bindBuffers.push_back(lastBBI); | 
|  | vertsNewPipeline(); | 
|  | if (checkAndAdvance(vertAppend(2), true))  { return; } | 
|  | dw->newDynamicState(); | 
|  | if (checkAndAdvance(vertAppend(3), false)) { return; } | 
|  | if (checkAndAdvance(vertAppend(1), true))  { return; } | 
|  | dw->newDynamicState(); | 
|  | if (checkAndAdvance(vertAppend(1), true))  { return; } | 
|  | vertsNewPipeline(); | 
|  | if (checkAndAdvance(vertAppend(1), false)) { return; } | 
|  | if (checkAndAdvance(vertAppend(1), true))  { return; } | 
|  | if (checkAndAdvance(vertAppend(1), true))  { return; } | 
|  | if (checkAndAdvance(vertAppend(1), true))  { return; } | 
|  | vertsNewPipeline(); | 
|  | if (checkAndAdvance(vertAppend(2), true))  { return; } | 
|  | if (checkAndAdvance(vertAppend(3), false)) { return; } | 
|  | if (checkAndAdvance(vertAppend(2), true))  { return; } | 
|  | dw->flush(); | 
|  |  | 
|  | constexpr uint32_t P = 0; | 
|  | constexpr uint32_t kExpectedBufferCount = 4; | 
|  | std::array<std::array<uint32_t, kReadbackSize>, kExpectedBufferCount> expected = {{ | 
|  | {V, P, P, P, V, V, P, P}, | 
|  | {V, V, V, V, V, P, P, P}, | 
|  | {V, V, V, V, V, V, P, P}, | 
|  | {V, V, V, V, V, P, P, P} | 
|  | }}; | 
|  |  | 
|  | skia_private::TArray<uint32_t> readbackData; | 
|  | std::array<sk_sp<Buffer>, kExpectedBufferCount> copyBuffers; | 
|  | if (!mappableDrawBuffers) { | 
|  | for(uint32_t i = 0; i < kExpectedBufferCount; ++i) { | 
|  | copyBuffers[i] = get_readback_buffer(recorder.get(), kVertBuffSize); | 
|  | recorder->priv().add(CopyBufferToBufferTask::Make(bindBuffers[i].fBuffer, | 
|  | /*srcOffset=*/0, | 
|  | copyBuffers[i], | 
|  | /*dstOffset=*/0, | 
|  | kVertBuffSize)); | 
|  | } | 
|  | } | 
|  | std::unique_ptr<Recording> recording = submit_recording(context, testContext, recorder.get()); | 
|  |  | 
|  | for (uint32_t i = 0; i < kExpectedBufferCount; ++i) { | 
|  | if (!map_and_readback_buffer_sync( | 
|  | context, | 
|  | testContext, | 
|  | mappableDrawBuffers ? const_cast<Buffer*>(bindBuffers[i].fBuffer) : | 
|  | copyBuffers[i].get(), | 
|  | readbackData, | 
|  | kReadbackSize)) { | 
|  | ERRORF(reporter, "Bad readback, exiting early."); | 
|  | return; | 
|  | } | 
|  | for(uint32_t j = 0; j < static_cast<uint32_t>(readbackData.size()); ++j) { | 
|  | if (readbackData[j] != expected[i][j]){ | 
|  | ERRORF(reporter, "Error expected %u: Got: %u At ExpectedIndex: %u \n", | 
|  | expected[i][j], readbackData[j], i * kExpectedBufferCount + j); | 
|  | return; | 
|  | } | 
|  | } | 
|  | } | 
|  | } |