Add support to gpu command buffers to wrap an external command buffer.

Bug: skia:
Change-Id: Ic679d292f42c61f9f1c36315ae605504a0283306
Reviewed-on: https://skia-review.googlesource.com/c/179521
Commit-Queue: Greg Daniel <egdaniel@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
This commit is contained in:
Greg Daniel 2019-01-03 17:35:54 -05:00 committed by Skia Commit-Bot
parent 0ae5f90985
commit 070cbafd6b
8 changed files with 252 additions and 49 deletions

View File

@ -523,10 +523,16 @@ void GrRenderTargetOpList::fullClear(GrContext* context, const SkPMColor4f& colo
if (this->isEmpty() || !fTarget.get()->asRenderTargetProxy()->needsStencil()) {
this->deleteOps();
fDeferredProxies.reset();
// If the opList is using a render target which wraps a vulkan command buffer, we can't do a
// clear load since we cannot change the render pass that we are using. Thus we fall back to
// making a clear op in this case.
if (!fTarget.get()->asRenderTargetProxy()->wrapsVkSecondaryCB()) {
fColorLoadOp = GrLoadOp::kClear;
fLoadClearColor = color;
return;
}
}
std::unique_ptr<GrClearOp> op(GrClearOp::Make(context, GrFixedClip::Disabled(),
color, fTarget.get()));

View File

@ -58,8 +58,10 @@ void GrVkCommandBuffer::freeGPUData(GrVkGpu* gpu) const {
fTrackedRecordingResources[i]->unref(gpu);
}
GR_VK_CALL(gpu->vkInterface(), FreeCommandBuffers(gpu->device(), fCmdPool->vkCommandPool(), 1,
&fCmdBuffer));
if (!this->isWrapped()) {
GR_VK_CALL(gpu->vkInterface(), FreeCommandBuffers(gpu->device(), fCmdPool->vkCommandPool(),
1, &fCmdBuffer));
}
this->onFreeGPUData(gpu);
}
@ -131,6 +133,7 @@ void GrVkCommandBuffer::pipelineBarrier(const GrVkGpu* gpu,
bool byRegion,
BarrierType barrierType,
void* barrier) const {
SkASSERT(!this->isWrapped());
SkASSERT(fIsActive);
// For images we can have barriers inside of render passes but they require us to add more
// support in subpasses which need self dependencies to have barriers inside them. Also, we can
@ -835,6 +838,7 @@ void GrVkPrimaryCommandBuffer::onAbandonGPUData() const {
GrVkSecondaryCommandBuffer* GrVkSecondaryCommandBuffer::Create(const GrVkGpu* gpu,
GrVkCommandPool* cmdPool) {
SkASSERT(cmdPool);
const VkCommandBufferAllocateInfo cmdInfo = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, // sType
nullptr, // pNext
@ -853,6 +857,9 @@ GrVkSecondaryCommandBuffer* GrVkSecondaryCommandBuffer::Create(const GrVkGpu* gp
return new GrVkSecondaryCommandBuffer(cmdBuffer, cmdPool);
}
GrVkSecondaryCommandBuffer* GrVkSecondaryCommandBuffer::Create(VkCommandBuffer cmdBuffer) {
return new GrVkSecondaryCommandBuffer(cmdBuffer, nullptr);
}
void GrVkSecondaryCommandBuffer::begin(const GrVkGpu* gpu, const GrVkFramebuffer* framebuffer,
const GrVkRenderPass* compatibleRenderPass) {
@ -860,6 +867,7 @@ void GrVkSecondaryCommandBuffer::begin(const GrVkGpu* gpu, const GrVkFramebuffer
SkASSERT(compatibleRenderPass);
fActiveRenderPass = compatibleRenderPass;
if (!this->isWrapped()) {
VkCommandBufferInheritanceInfo inheritanceInfo;
memset(&inheritanceInfo, 0, sizeof(VkCommandBufferInheritanceInfo));
inheritanceInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
@ -881,12 +889,15 @@ void GrVkSecondaryCommandBuffer::begin(const GrVkGpu* gpu, const GrVkFramebuffer
GR_VK_CALL_ERRCHECK(gpu->vkInterface(), BeginCommandBuffer(fCmdBuffer,
&cmdBufferBeginInfo));
}
fIsActive = true;
}
void GrVkSecondaryCommandBuffer::end(GrVkGpu* gpu) {
SkASSERT(fIsActive);
if (!this->isWrapped()) {
GR_VK_CALL_ERRCHECK(gpu->vkInterface(), EndCommandBuffer(fCmdBuffer));
}
this->invalidateState();
fIsActive = false;
}

View File

@ -132,7 +132,7 @@ public:
protected:
GrVkCommandBuffer(VkCommandBuffer cmdBuffer, GrVkCommandPool* cmdPool,
const GrVkRenderPass* rp = VK_NULL_HANDLE)
const GrVkRenderPass* rp = nullptr)
: fIsActive(false)
, fActiveRenderPass(rp)
, fCmdBuffer(cmdBuffer)
@ -144,6 +144,10 @@ protected:
this->invalidateState();
}
bool isWrapped() const {
return fCmdPool == nullptr;
}
SkTDArray<const GrVkResource*> fTrackedResources;
SkTDArray<const GrVkRecycledResource*> fTrackedRecycledResources;
SkTDArray<const GrVkResource*> fTrackedRecordingResources;
@ -326,6 +330,8 @@ private:
class GrVkSecondaryCommandBuffer : public GrVkCommandBuffer {
public:
static GrVkSecondaryCommandBuffer* Create(const GrVkGpu* gpu, GrVkCommandPool* cmdPool);
// Used for wrapping an external secondary command buffer.
static GrVkSecondaryCommandBuffer* Create(VkCommandBuffer externalSecondaryCB);
void begin(const GrVkGpu* gpu, const GrVkFramebuffer* framebuffer,
const GrVkRenderPass* compatibleRenderPass);

View File

@ -127,6 +127,21 @@ void GrVkGpuRTCommandBuffer::init() {
cbInfo.currentCmdBuf()->begin(fGpu, vkRT->framebuffer(), cbInfo.fRenderPass);
}
void GrVkGpuRTCommandBuffer::initWrapped() {
CommandBufferInfo& cbInfo = fCommandBufferInfos.push_back();
SkASSERT(fCommandBufferInfos.count() == 1);
fCurrentCmdInfo = 0;
GrVkRenderTarget* vkRT = static_cast<GrVkRenderTarget*>(fRenderTarget);
SkASSERT(vkRT->wrapsSecondaryCommandBuffer());
cbInfo.fRenderPass = vkRT->externalRenderPass();
cbInfo.fRenderPass->ref();
cbInfo.fBounds.setEmpty();
cbInfo.fCommandBuffers.push_back(vkRT->getExternalSecondaryCommandBuffer());
cbInfo.fCommandBuffers[0]->ref();
cbInfo.currentCmdBuf()->begin(fGpu, nullptr, cbInfo.fRenderPass);
}
GrVkGpuRTCommandBuffer::~GrVkGpuRTCommandBuffer() {
this->reset();
@ -176,6 +191,23 @@ void GrVkGpuRTCommandBuffer::submit() {
continue;
}
// We don't want to actually submit the secondary command buffer if it is wrapped.
if (this->wrapsSecondaryCommandBuffer()) {
// If we have any sampled images set their layout now.
for (int j = 0; j < cbInfo.fSampledImages.count(); ++j) {
cbInfo.fSampledImages[j]->setImageLayout(fGpu,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
false);
}
// There should have only been one secondary command buffer in the wrapped case so it is
// safe to just return here.
SkASSERT(fCommandBufferInfos.count() == 1);
return;
}
// Make sure if we only have a discard load that we execute the discard on the whole image.
// TODO: Once we improve our tracking of discards so that we never end up flushing a discard
// call with no actually ops, remove this.
@ -244,6 +276,11 @@ void GrVkGpuRTCommandBuffer::set(GrRenderTarget* rt, GrSurfaceOrigin origin,
this->INHERITED::set(rt, origin);
if (this->wrapsSecondaryCommandBuffer()) {
this->initWrapped();
return;
}
fClearColor = colorInfo.fClearColor;
get_vk_load_store_ops(colorInfo.fLoadOp, colorInfo.fStoreOp,
@ -271,6 +308,11 @@ void GrVkGpuRTCommandBuffer::reset() {
fRenderTarget = nullptr;
}
bool GrVkGpuRTCommandBuffer::wrapsSecondaryCommandBuffer() const {
GrVkRenderTarget* vkRT = static_cast<GrVkRenderTarget*>(fRenderTarget);
return vkRT->wrapsSecondaryCommandBuffer();
}
////////////////////////////////////////////////////////////////////////////////
void GrVkGpuRTCommandBuffer::discard() {

View File

@ -87,6 +87,12 @@ public:
private:
void init();
// Called instead of init when we are drawing to a render target that already wraps a secondary
// command buffer.
void initWrapped();
bool wrapsSecondaryCommandBuffer() const;
GrGpu* gpu() override;
// Bind vertex and index buffers

View File

@ -115,7 +115,7 @@ GrVkRenderTarget::GrVkRenderTarget(GrVkGpu* gpu,
const GrVkImageInfo& info,
sk_sp<GrVkImageLayout> layout,
const GrVkRenderPass* renderPass,
VkCommandBuffer secondaryCommandBuffer)
GrVkSecondaryCommandBuffer* secondaryCommandBuffer)
: GrSurface(gpu, desc)
, GrVkImage(info, std::move(layout), GrBackendObjectOwnership::kBorrowed, true)
, GrRenderTarget(gpu, desc)
@ -219,8 +219,13 @@ sk_sp<GrVkRenderTarget> GrVkRenderTarget::MakeSecondaryCBRenderTarget(
return nullptr;
}
GrVkRenderTarget* vkRT = new GrVkRenderTarget(gpu, desc, info, std::move(layout), rp,
vkInfo.fSecondaryCommandBuffer);
GrVkSecondaryCommandBuffer* scb =
GrVkSecondaryCommandBuffer::Create(vkInfo.fSecondaryCommandBuffer);
if (!scb) {
return nullptr;
}
GrVkRenderTarget* vkRT = new GrVkRenderTarget(gpu, desc, info, std::move(layout), rp, scb);
return sk_sp<GrVkRenderTarget>(vkRT);
}
@ -284,6 +289,7 @@ GrVkRenderTarget::~GrVkRenderTarget() {
SkASSERT(!fColorAttachmentView);
SkASSERT(!fFramebuffer);
SkASSERT(!fCachedSimpleRenderPass);
SkASSERT(!fSecondaryCommandBuffer);
}
void GrVkRenderTarget::addResources(GrVkCommandBuffer& commandBuffer) const {
@ -321,6 +327,10 @@ void GrVkRenderTarget::releaseInternalObjects() {
fCachedSimpleRenderPass->unref(gpu);
fCachedSimpleRenderPass = nullptr;
}
if (fSecondaryCommandBuffer) {
fSecondaryCommandBuffer->unref(gpu);
fSecondaryCommandBuffer = nullptr;
}
}
void GrVkRenderTarget::abandonInternalObjects() {
@ -345,6 +355,10 @@ void GrVkRenderTarget::abandonInternalObjects() {
fCachedSimpleRenderPass->unrefAndAbandon();
fCachedSimpleRenderPass = nullptr;
}
if (fSecondaryCommandBuffer) {
fSecondaryCommandBuffer->unrefAndAbandon();
fSecondaryCommandBuffer = nullptr;
}
}
void GrVkRenderTarget::onRelease() {

View File

@ -20,6 +20,7 @@ class GrVkCommandBuffer;
class GrVkFramebuffer;
class GrVkGpu;
class GrVkImageView;
class GrVkSecondaryCommandBuffer;
class GrVkStencilAttachment;
struct GrVkImageInfo;
@ -67,7 +68,10 @@ public:
return fCachedSimpleRenderPass;
}
bool wrapsSecondaryCommandBuffer() const { return fSecondaryCommandBuffer != VK_NULL_HANDLE; }
bool wrapsSecondaryCommandBuffer() const { return fSecondaryCommandBuffer != nullptr; }
GrVkSecondaryCommandBuffer* getExternalSecondaryCommandBuffer() const {
return fSecondaryCommandBuffer;
}
// override of GrRenderTarget
ResolveType getResolveType() const override {
@ -152,7 +156,7 @@ private:
const GrVkImageInfo& info,
sk_sp<GrVkImageLayout> layout,
const GrVkRenderPass* renderPass,
VkCommandBuffer secondaryCommandBuffer);
GrVkSecondaryCommandBuffer* secondaryCommandBuffer);
bool completeStencilAttachment() override;
@ -167,10 +171,11 @@ private:
// This is a handle to be used to quickly get compatible GrVkRenderPasses for this render target
GrVkResourceProvider::CompatibleRPHandle fCompatibleRPHandle;
// Handle to an external secondary command buffer which this GrVkRenderTarget represents. If
// this is not VK_NULL_HANDLE then the GrVkRenderTarget does not have a real VkImage backing it,
// and is limited in what it can be used for.
VkCommandBuffer fSecondaryCommandBuffer = VK_NULL_HANDLE;
// If this render target wraps an external VkCommandBuffer, then this pointer will be non-null
// and will point to the GrVk object that, in turn, wraps the external VkCommandBuffer. In this
// case the render target will not be backed by an actual VkImage and will thus be limited in
// terms of what it can be used for.
GrVkSecondaryCommandBuffer* fSecondaryCommandBuffer = nullptr;
};
#endif

View File

@ -22,6 +22,7 @@
#include "vk/GrVkGpu.h"
#include "vk/GrVkInterface.h"
#include "vk/GrVkMemory.h"
#include "vk/GrVkSecondaryCBDrawContext.h"
#include "vk/GrVkUtil.h"
using sk_gpu_test::GrContextFactory;
@ -30,22 +31,23 @@ static const int DEV_W = 16, DEV_H = 16;
class TestDrawable : public SkDrawable {
public:
TestDrawable(const GrVkInterface* interface, int32_t width, int32_t height)
TestDrawable(const GrVkInterface* interface, GrContext* context, int32_t width, int32_t height)
: INHERITED()
, fInterface(interface)
, fContext(context)
, fWidth(width)
, fHeight(height) {}
~TestDrawable() override {}
class DrawHandler : public GpuDrawHandler {
class DrawHandlerBasic : public GpuDrawHandler {
public:
DrawHandler(const GrVkInterface* interface, int32_t width, int32_t height)
DrawHandlerBasic(const GrVkInterface* interface, int32_t width, int32_t height)
: INHERITED()
, fInterface(interface)
, fWidth(width)
, fHeight(height) {}
~DrawHandler() override {}
~DrawHandlerBasic() override {}
void draw(const GrBackendDrawableInfo& info) override {
GrVkDrawableInfo vkInfo;
@ -86,14 +88,100 @@ public:
typedef GpuDrawHandler INHERITED;
};
typedef void (*DrawProc)(TestDrawable*, const GrVkDrawableInfo&);
typedef void (*SubmitProc)(TestDrawable*);
// Exercises the exporting of a secondary command buffer from one GrContext and then importing
// it into a second GrContext. We then draw to the secondary command buffer from the second
// GrContext.
class DrawHandlerImport : public GpuDrawHandler {
public:
DrawHandlerImport(TestDrawable* td, DrawProc drawProc, SubmitProc submitProc)
: INHERITED()
, fTestDrawable(td)
, fDrawProc(drawProc)
, fSubmitProc(submitProc) {}
~DrawHandlerImport() override {
fSubmitProc(fTestDrawable);
}
void draw(const GrBackendDrawableInfo& info) override {
GrVkDrawableInfo vkInfo;
SkAssertResult(info.getVkDrawableInfo(&vkInfo));
fDrawProc(fTestDrawable, vkInfo);
}
private:
TestDrawable* fTestDrawable;
DrawProc fDrawProc;
SubmitProc fSubmitProc;
typedef GpuDrawHandler INHERITED;
};
// Helper function to test drawing to a secondary command buffer that we imported into the
// GrContext using a GrVkSecondaryCBDrawContext.
static void ImportDraw(TestDrawable* td, const GrVkDrawableInfo& info) {
SkImageInfo imageInfo = SkImageInfo::Make(td->fWidth, td->fHeight, kRGBA_8888_SkColorType,
kPremul_SkAlphaType);
td->fDrawContext = GrVkSecondaryCBDrawContext::Make(td->fContext, imageInfo, info, nullptr);
if (!td->fDrawContext) {
return;
}
SkCanvas* canvas = td->fDrawContext->getCanvas();
SkIRect rect = SkIRect::MakeXYWH(td->fWidth/2, 0, td->fWidth/4, td->fHeight);
SkPaint paint;
paint.setColor(SK_ColorRED);
canvas->drawIRect(rect, paint);
// Draw to an offscreen target so that we end up with a mix of "real" secondary command
// buffers and the imported secondary command buffer.
sk_sp<SkSurface> surf = SkSurface::MakeRenderTarget(td->fContext, SkBudgeted::kYes,
imageInfo);
surf->getCanvas()->clear(SK_ColorRED);
SkRect dstRect = SkRect::MakeXYWH(3*td->fWidth/4, 0, td->fWidth/4, td->fHeight);
SkIRect srcRect = SkIRect::MakeWH(td->fWidth/4, td->fHeight);
canvas->drawImageRect(surf->makeImageSnapshot(), srcRect, dstRect, &paint);
td->fDrawContext->flush();
}
// Helper function to test waiting for the imported secondary command buffer to be submitted on
// its original context and then cleaning up the GrVkSecondaryCBDrawContext from this GrContext.
static void ImportSubmitted(TestDrawable* td) {
// Typical use case here would be to create a fence that we submit to the gpu and then wait
// on before releasing the GrVkSecondaryCBDrawContext resources. To simulate that for this
// test (and since we are running single threaded anyways), we will just force a sync of
// the gpu and cpu here.
td->fContext->contextPriv().getGpu()->testingOnly_flushGpuAndSync();
td->fDrawContext->releaseResources();
// We release the GrContext here manually to test that we waited long enough before
// releasing the GrVkSecondaryCBDrawContext. This simulates when a client is able to delete
// the GrContext it used to imported the secondary command buffer. If we had released the
// GrContext's resources earlier (before waiting on the gpu above), we would get vulkan
// validation layer errors saying we freed some vulkan objects while they were still in use
// on the GPU.
td->fContext->releaseResourcesAndAbandonContext();
}
std::unique_ptr<GpuDrawHandler> onSnapGpuDrawHandler(GrBackendApi backendApi,
const SkMatrix& matrix,
const SkIRect& clipBounds) override {
if (backendApi != GrBackendApi::kVulkan) {
return nullptr;
}
std::unique_ptr<DrawHandler> draw(new DrawHandler(fInterface, fWidth, fHeight));
return std::move(draw);
std::unique_ptr<GpuDrawHandler> draw;
if (fContext) {
draw.reset(new DrawHandlerImport(this, ImportDraw, ImportSubmitted));
} else {
draw.reset(new DrawHandlerBasic(fInterface, fWidth, fHeight));
}
return draw;
}
SkRect onGetBounds() override {
@ -106,13 +194,15 @@ public:
private:
const GrVkInterface* fInterface;
GrContext* fContext;
sk_sp<GrVkSecondaryCBDrawContext> fDrawContext;
int32_t fWidth;
int32_t fHeight;
typedef SkDrawable INHERITED;
};
void draw_drawable_test(skiatest::Reporter* reporter, GrContext* context) {
void draw_drawable_test(skiatest::Reporter* reporter, GrContext* context, GrContext* childContext) {
GrVkGpu* gpu = static_cast<GrVkGpu*>(context->contextPriv().getGpu());
const SkImageInfo ii = SkImageInfo::Make(DEV_W, DEV_H, kRGBA_8888_SkColorType,
@ -122,7 +212,7 @@ void draw_drawable_test(skiatest::Reporter* reporter, GrContext* context) {
SkCanvas* canvas = surface->getCanvas();
canvas->clear(SK_ColorBLUE);
sk_sp<TestDrawable> drawable(new TestDrawable(gpu->vkInterface(), DEV_W, DEV_H));
sk_sp<TestDrawable> drawable(new TestDrawable(gpu->vkInterface(), childContext, DEV_W, DEV_H));
canvas->drawDrawable(drawable.get());
SkPaint paint;
@ -138,8 +228,8 @@ void draw_drawable_test(skiatest::Reporter* reporter, GrContext* context) {
const uint32_t* canvasPixels = static_cast<const uint32_t*>(bitmap.getPixels());
bool failureFound = false;
SkPMColor expectedPixel;
for (int cy = 0; cy < DEV_H || failureFound; ++cy) {
for (int cx = 0; cx < DEV_W || failureFound; ++cx) {
for (int cy = 0; cy < DEV_H && !failureFound; ++cy) {
for (int cx = 0; cx < DEV_W && !failureFound; ++cx) {
SkPMColor canvasPixel = canvasPixels[cy * DEV_W + cx];
if (cy < DEV_H / 2) {
if (cx < DEV_W / 2) {
@ -159,9 +249,32 @@ void draw_drawable_test(skiatest::Reporter* reporter, GrContext* context) {
}
}
DEF_GPUTEST_FOR_VULKAN_CONTEXT(VkDrawableTest, reporter, ctxInfo) {
draw_drawable_test(reporter, ctxInfo.grContext());
draw_drawable_test(reporter, ctxInfo.grContext(), nullptr);
}
DEF_GPUTEST(VkDrawableImportTest, reporter, options) {
for (int typeInt = 0; typeInt < sk_gpu_test::GrContextFactory::kContextTypeCnt; ++typeInt) {
sk_gpu_test::GrContextFactory::ContextType contextType =
(sk_gpu_test::GrContextFactory::ContextType) typeInt;
if (contextType != sk_gpu_test::GrContextFactory::kVulkan_ContextType) {
continue;
}
sk_gpu_test::GrContextFactory factory(options);
sk_gpu_test::ContextInfo ctxInfo = factory.getContextInfo(
contextType, sk_gpu_test::GrContextFactory::ContextOverrides::kDisableNVPR);
skiatest::ReporterContext ctx(
reporter, SkString(sk_gpu_test::GrContextFactory::ContextTypeName(contextType)));
if (ctxInfo.grContext()) {
sk_gpu_test::ContextInfo child =
factory.getSharedContextInfo(ctxInfo.grContext(), 0);
if (!child.grContext()) {
continue;
}
draw_drawable_test(reporter, ctxInfo.grContext(), child.grContext());
}
}
}
#endif