diff options
author | 2016-02-22 09:11:32 -0500 | |
---|---|---|
committer | 2016-02-22 09:11:32 -0500 | |
commit | 48cf268defad66f58f1aa03b4835e5583be96b2f (patch) | |
tree | d6ce2161cae1b69ca7a595110185652297464696 /src/gpu/vk/GrVkGpu.cpp | |
parent | 1d524699274bc1d4c43af0c3d0ae269b8c37a3bb (diff) |
Add vulkan files into skia repo. This is an incomplete backend with only partial functionality at this time.
R=robertphillips@google.com
TBR=bsalomon@google.com
BUG=skia:4955
GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1718693002
Review URL: https://codereview.chromium.org/1718693002
Diffstat (limited to 'src/gpu/vk/GrVkGpu.cpp')
-rw-r--r-- | src/gpu/vk/GrVkGpu.cpp | 1272 |
1 files changed, 1272 insertions, 0 deletions
diff --git a/src/gpu/vk/GrVkGpu.cpp b/src/gpu/vk/GrVkGpu.cpp new file mode 100644 index 0000000000..ef2d0be615 --- /dev/null +++ b/src/gpu/vk/GrVkGpu.cpp @@ -0,0 +1,1272 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "GrVkGpu.h" + +#include "GrContextOptions.h" +#include "GrGeometryProcessor.h" +#include "GrGpuResourceCacheAccess.h" +#include "GrPipeline.h" +#include "GrRenderTargetPriv.h" +#include "GrSurfacePriv.h" +#include "GrTexturePriv.h" +#include "GrVertices.h" + +#include "GrVkCommandBuffer.h" +#include "GrVkImage.h" +#include "GrVkIndexBuffer.h" +#include "GrVkMemory.h" +#include "GrVkPipeline.h" +#include "GrVkProgram.h" +#include "GrVkProgramBuilder.h" +#include "GrVkProgramDesc.h" +#include "GrVkRenderPass.h" +#include "GrVkResourceProvider.h" +#include "GrVkTexture.h" +#include "GrVkTextureRenderTarget.h" +#include "GrVkTransferBuffer.h" +#include "GrVkVertexBuffer.h" + +#include "SkConfig8888.h" + +#include "vk/GrVkInterface.h" + +#define VK_CALL(X) GR_VK_CALL(this->vkInterface(), X) +#define VK_CALL_RET(RET, X) GR_VK_CALL_RET(this->vkInterface(), RET, X) +#define VK_CALL_ERRCHECK(X) GR_VK_CALL_ERRCHECK(this->vkInterface(), X) + +//////////////////////////////////////////////////////////////////////////////// +// Stuff used to set up a GrVkGpu secrectly for now. + +// For now the VkGpuCreate is using the same signature as GL. This is mostly for ease of +// hiding this code from offical skia. In the end the VkGpuCreate will not take a GrBackendContext +// and mostly likely would take an optional device and queues to use. +GrGpu* vk_gpu_create(GrBackendContext backendContext, const GrContextOptions& options, + GrContext* context) { + // Below is Vulkan setup code that normal would be done by a client, but will do here for now + // for testing purposes. + VkPhysicalDevice physDev; + VkDevice device; + VkInstance inst; + VkResult err; + + const VkApplicationInfo app_info = { + VK_STRUCTURE_TYPE_APPLICATION_INFO, // sType + nullptr, // pNext + "vktest", // pApplicationName + 0, // applicationVersion + "vktest", // pEngineName + 0, // engineVerison + VK_API_VERSION, // apiVersion + }; + const VkInstanceCreateInfo instance_create = { + VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, // sType + nullptr, // pNext + 0, // flags + &app_info, // pApplicationInfo + 0, // enabledLayerNameCount + nullptr, // ppEnabledLayerNames + 0, // enabledExtensionNameCount + nullptr, // ppEnabledExtensionNames + }; + err = vkCreateInstance(&instance_create, nullptr, &inst); + if (err < 0) { + SkDebugf("vkCreateInstanced failed: %d\n", err); + SkFAIL("failing"); + } + + uint32_t gpuCount; + err = vkEnumeratePhysicalDevices(inst, &gpuCount, nullptr); + if (err) { + SkDebugf("vkEnumeratePhysicalDevices failed: %d\n", err); + SkFAIL("failing"); + } + SkASSERT(gpuCount > 0); + // Just returning the first physical device instead of getting the whole array. + gpuCount = 1; + err = vkEnumeratePhysicalDevices(inst, &gpuCount, &physDev); + if (err) { + SkDebugf("vkEnumeratePhysicalDevices failed: %d\n", err); + SkFAIL("failing"); + } + + // query to get the initial queue props size + uint32_t queueCount; + vkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, nullptr); + SkASSERT(queueCount >= 1); + + SkAutoMalloc queuePropsAlloc(queueCount * sizeof(VkQueueFamilyProperties)); + // now get the actual queue props + VkQueueFamilyProperties* queueProps = (VkQueueFamilyProperties*)queuePropsAlloc.get(); + + vkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, queueProps); + + // iterate to find the graphics queue + uint32_t graphicsQueueIndex = -1; + for (uint32_t i = 0; i < queueCount; i++) { + if (queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + graphicsQueueIndex = i; + break; + } + } + SkASSERT(graphicsQueueIndex < queueCount); + + float queuePriorities[1] = { 0.0 }; + const VkDeviceQueueCreateInfo queueInfo = { + VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, // sType + nullptr, // pNext + 0, // VkDeviceQueueCreateFlags + 0, // queueFamilyIndex + 1, // queueCount + queuePriorities, // pQueuePriorities + }; + const VkDeviceCreateInfo deviceInfo = { + VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, // sType + nullptr, // pNext + 0, // VkDeviceCreateFlags + 1, // queueCreateInfoCount + &queueInfo, // pQueueCreateInfos + 0, // layerCount + nullptr, // ppEnabledLayerNames + 0, // extensionCount + nullptr, // ppEnabledExtensionNames + nullptr // ppEnabledFeatures + }; + + err = vkCreateDevice(physDev, &deviceInfo, nullptr, &device); + if (err) { + SkDebugf("CreateDevice failed: %d\n", err); + SkFAIL("failing"); + } + + VkQueue queue; + vkGetDeviceQueue(device, graphicsQueueIndex, 0, &queue); + + const VkCommandPoolCreateInfo cmdPoolInfo = { + VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, // sType + nullptr, // pNext + 0, // CmdPoolCreateFlags + graphicsQueueIndex, // queueFamilyIndex + }; + + VkCommandPool cmdPool; + err = vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &cmdPool); + if (err) { + SkDebugf("CreateCommandPool failed: %d\n", err); + SkFAIL("failing"); + } + + return new GrVkGpu(context, options, physDev, device, queue, cmdPool, inst); +} + +//////////////////////////////////////////////////////////////////////////////// + +GrVkGpu::GrVkGpu(GrContext* context, const GrContextOptions& options, + VkPhysicalDevice physDev, VkDevice device, VkQueue queue, VkCommandPool cmdPool, + VkInstance inst) + : INHERITED(context) + , fDevice(device) + , fQueue(queue) + , fCmdPool(cmdPool) + , fResourceProvider(this) + , fVkInstance(inst) { + fInterface.reset(GrVkCreateInterface(fVkInstance)); + fCompiler = shaderc_compiler_initialize(); + + fVkCaps.reset(new GrVkCaps(options, fInterface, physDev)); + fCaps.reset(SkRef(fVkCaps.get())); + + fCurrentCmdBuffer = fResourceProvider.createCommandBuffer(); + SkASSERT(fCurrentCmdBuffer); + fCurrentCmdBuffer->begin(this); + VK_CALL(GetPhysicalDeviceMemoryProperties(physDev, &fPhysDevMemProps)); + +} + +GrVkGpu::~GrVkGpu() { + shaderc_compiler_release(fCompiler); + fCurrentCmdBuffer->end(this); + fCurrentCmdBuffer->unref(this); + + // wait for all commands to finish + VK_CALL(QueueWaitIdle(fQueue)); + + // must call this just before we destroy the VkDevice + fResourceProvider.destroyResources(); + + VK_CALL(DestroyCommandPool(fDevice, fCmdPool, nullptr)); + VK_CALL(DestroyDevice(fDevice, nullptr)); + VK_CALL(DestroyInstance(fVkInstance, nullptr)); +} + +/////////////////////////////////////////////////////////////////////////////// + +void GrVkGpu::submitCommandBuffer(SyncQueue sync) { + SkASSERT(fCurrentCmdBuffer); + fCurrentCmdBuffer->end(this); + + fCurrentCmdBuffer->submitToQueue(this, fQueue, sync); + fResourceProvider.checkCommandBuffers(); + + // Release old command buffer and create a new one + fCurrentCmdBuffer->unref(this); + fCurrentCmdBuffer = fResourceProvider.createCommandBuffer(); + SkASSERT(fCurrentCmdBuffer); + + fCurrentCmdBuffer->begin(this); +} + +/////////////////////////////////////////////////////////////////////////////// +GrVertexBuffer* GrVkGpu::onCreateVertexBuffer(size_t size, bool dynamic) { + return GrVkVertexBuffer::Create(this, size, dynamic); +} + +GrIndexBuffer* GrVkGpu::onCreateIndexBuffer(size_t size, bool dynamic) { + return GrVkIndexBuffer::Create(this, size, dynamic); +} + +GrTransferBuffer* GrVkGpu::onCreateTransferBuffer(size_t size, TransferType type) { + GrVkBuffer::Type bufferType = kCpuToGpu_TransferType ? GrVkBuffer::kCopyRead_Type + : GrVkBuffer::kCopyWrite_Type; + return GrVkTransferBuffer::Create(this, size, bufferType); +} + +//////////////////////////////////////////////////////////////////////////////// +bool GrVkGpu::onGetWritePixelsInfo(GrSurface* dstSurface, int width, int height, + GrPixelConfig srcConfig, DrawPreference* drawPreference, + WritePixelTempDrawInfo* tempDrawInfo) { + if (kIndex_8_GrPixelConfig == srcConfig || GrPixelConfigIsCompressed(dstSurface->config())) { + return false; + } + + // Currently we don't handle draws, so if the caller wants/needs to do a draw we need to fail + if (kNoDraw_DrawPreference != *drawPreference) { + return false; + } + + if (dstSurface->config() != srcConfig) { + // TODO: This should fall back to drawing or copying to change config of dstSurface to + // match that of srcConfig. + return false; + } + + return true; +} + +bool GrVkGpu::onWritePixels(GrSurface* surface, + int left, int top, int width, int height, + GrPixelConfig config, const void* buffer, + size_t rowBytes) { + GrVkTexture* vkTex = static_cast<GrVkTexture*>(surface->asTexture()); + if (!vkTex) { + return false; + } + + // We assume Vulkan doesn't do sRGB <-> linear conversions when reading and writing pixels. + if (GrPixelConfigIsSRGB(surface->config()) != GrPixelConfigIsSRGB(config)) { + return false; + } + + bool success = false; + if (GrPixelConfigIsCompressed(vkTex->desc().fConfig)) { + // We check that config == desc.fConfig in GrGpu::getWritePixelsInfo() + SkASSERT(config == vkTex->desc().fConfig); + // TODO: add compressed texture support + // delete the following two lines and uncomment the two after that when ready + vkTex->unref(); + return false; + //success = this->uploadCompressedTexData(vkTex->desc(), buffer, false, left, top, width, + // height); + } else { + bool linearTiling = vkTex->isLinearTiled(); + if (linearTiling && VK_IMAGE_LAYOUT_PREINITIALIZED != vkTex->currentLayout()) { + // Need to change the layout to general in order to perform a host write + VkImageLayout layout = vkTex->currentLayout(); + VkPipelineStageFlags srcStageMask = GrVkMemory::LayoutToPipelineStageFlags(layout); + VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_HOST_BIT; + VkAccessFlags srcAccessMask = GrVkMemory::LayoutToSrcAccessMask(layout); + VkAccessFlags dstAccessMask = VK_ACCESS_HOST_WRITE_BIT; + vkTex->setImageLayout(this, + VK_IMAGE_LAYOUT_GENERAL, + srcAccessMask, + dstAccessMask, + srcStageMask, + dstStageMask, + false); + } + success = this->uploadTexData(vkTex, left, top, width, height, config, + buffer, rowBytes); + } + + if (success) { + vkTex->texturePriv().dirtyMipMaps(true); + return true; + } + + return false; +} + +bool GrVkGpu::uploadTexData(GrVkTexture* tex, + int left, int top, int width, int height, + GrPixelConfig dataConfig, + const void* data, + size_t rowBytes) { + SkASSERT(data); + + // If we're uploading compressed data then we should be using uploadCompressedTexData + SkASSERT(!GrPixelConfigIsCompressed(dataConfig)); + + bool linearTiling = tex->isLinearTiled(); + + size_t bpp = GrBytesPerPixel(dataConfig); + + const GrSurfaceDesc& desc = tex->desc(); + + if (!GrSurfacePriv::AdjustWritePixelParams(desc.fWidth, desc.fHeight, bpp, &left, &top, + &width, &height, &data, &rowBytes)) { + return false; + } + size_t trimRowBytes = width * bpp; + + if (linearTiling) { + SkASSERT(VK_IMAGE_LAYOUT_PREINITIALIZED == tex->currentLayout() || + VK_IMAGE_LAYOUT_GENERAL == tex->currentLayout()); + const VkImageSubresource subres = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, // mipLevel + 0, // arraySlice + }; + VkSubresourceLayout layout; + VkResult err; + + const GrVkInterface* interface = this->vkInterface(); + + GR_VK_CALL(interface, GetImageSubresourceLayout(fDevice, + tex->textureImage(), + &subres, + &layout)); + + int texTop = kBottomLeft_GrSurfaceOrigin == desc.fOrigin ? tex->height() - top - height + : top; + VkDeviceSize offset = texTop*layout.rowPitch + left*bpp; + VkDeviceSize size = height*layout.rowPitch; + void* mapPtr; + err = GR_VK_CALL(interface, MapMemory(fDevice, tex->textureMemory(), offset, size, 0, + &mapPtr)); + if (err) { + return false; + } + + if (kBottomLeft_GrSurfaceOrigin == desc.fOrigin) { + // copy into buffer by rows + const char* srcRow = reinterpret_cast<const char*>(data); + char* dstRow = reinterpret_cast<char*>(mapPtr)+(height - 1)*layout.rowPitch; + for (int y = 0; y < height; y++) { + memcpy(dstRow, srcRow, trimRowBytes); + srcRow += rowBytes; + dstRow -= layout.rowPitch; + } + } else { + // If there is no padding on the src (rowBytes) or dst (layout.rowPitch) we can memcpy + if (trimRowBytes == rowBytes && trimRowBytes == layout.rowPitch) { + memcpy(mapPtr, data, trimRowBytes * height); + } else { + SkRectMemcpy(mapPtr, layout.rowPitch, data, rowBytes, trimRowBytes, height); + } + } + + GR_VK_CALL(interface, UnmapMemory(fDevice, tex->textureMemory())); + } else { + GrVkTransferBuffer* transferBuffer = + GrVkTransferBuffer::Create(this, trimRowBytes * height, GrVkBuffer::kCopyRead_Type); + + void* mapPtr = transferBuffer->map(); + + if (kBottomLeft_GrSurfaceOrigin == desc.fOrigin) { + // copy into buffer by rows + const char* srcRow = reinterpret_cast<const char*>(data); + char* dstRow = reinterpret_cast<char*>(mapPtr)+(height - 1)*trimRowBytes; + for (int y = 0; y < height; y++) { + memcpy(dstRow, srcRow, trimRowBytes); + srcRow += rowBytes; + dstRow -= trimRowBytes; + } + } else { + // If there is no padding on the src data rows, we can do a single memcpy + if (trimRowBytes == rowBytes) { + memcpy(mapPtr, data, trimRowBytes * height); + } else { + SkRectMemcpy(mapPtr, trimRowBytes, data, rowBytes, trimRowBytes, height); + } + } + + transferBuffer->unmap(); + + // make sure the unmap has finished + transferBuffer->addMemoryBarrier(this, + VK_ACCESS_HOST_WRITE_BIT, + VK_ACCESS_TRANSFER_READ_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + false); + + // Set up copy region + bool flipY = kBottomLeft_GrSurfaceOrigin == tex->origin(); + VkOffset3D offset = { + left, + flipY ? tex->height() - top - height : top, + 0 + }; + + VkBufferImageCopy region; + memset(®ion, 0, sizeof(VkBufferImageCopy)); + region.bufferOffset = 0; + region.bufferRowLength = width; + region.bufferImageHeight = height; + region.imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }; + region.imageOffset = offset; + region.imageExtent = { (uint32_t)width, (uint32_t)height, 1 }; + + // Change layout of our target so it can be copied to + VkImageLayout layout = tex->currentLayout(); + VkPipelineStageFlags srcStageMask = GrVkMemory::LayoutToPipelineStageFlags(layout); + VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; + VkAccessFlags srcAccessMask = GrVkMemory::LayoutToSrcAccessMask(layout); + VkAccessFlags dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + tex->setImageLayout(this, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + srcAccessMask, + dstAccessMask, + srcStageMask, + dstStageMask, + false); + + // Copy the buffer to the image + fCurrentCmdBuffer->copyBufferToImage(this, + transferBuffer, + tex, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ®ion); + + // Submit the current command buffer to the Queue + this->submitCommandBuffer(kSkip_SyncQueue); + + transferBuffer->unref(); + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +GrTexture* GrVkGpu::onCreateTexture(const GrSurfaceDesc& desc, GrGpuResource::LifeCycle lifeCycle, + const void* srcData, size_t rowBytes) { + bool renderTarget = SkToBool(desc.fFlags & kRenderTarget_GrSurfaceFlag); + + VkFormat pixelFormat; + if (!GrPixelConfigToVkFormat(desc.fConfig, &pixelFormat)) { + return nullptr; + } + + if (!fVkCaps->isConfigTexturable(desc.fConfig)) { + return nullptr; + } + + bool linearTiling = false; + if (SkToBool(desc.fFlags & kZeroCopy_GrSurfaceFlag)) { + if (fVkCaps->isConfigTexurableLinearly(desc.fConfig) && + (!renderTarget || fVkCaps->isConfigRenderableLinearly(desc.fConfig, false))) { + linearTiling = true; + } else { + return nullptr; + } + } + + VkImageUsageFlags usageFlags = VK_IMAGE_USAGE_SAMPLED_BIT; + if (renderTarget) { + usageFlags |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + } + + // For now we will set the VK_IMAGE_USAGE_TRANSFER_DESTINATION_BIT and + // VK_IMAGE_USAGE_TRANSFER_SOURCE_BIT on every texture since we do not know whether or not we + // will be using this texture in some copy or not. Also this assumes, as is the current case, + // that all render targets in vulkan are also texutres. If we change this practice of setting + // both bits, we must make sure to set the destination bit if we are uploading srcData to the + // texture. + usageFlags |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + + VkFlags memProps = (srcData && linearTiling) ? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT : + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + + // This ImageDesc refers to the texture that will be read by the client. Thus even if msaa is + // requested, this ImageDesc describes the resolved texutre. Therefore we always have samples set + // to 1. + GrVkImage::ImageDesc imageDesc; + imageDesc.fImageType = VK_IMAGE_TYPE_2D; + imageDesc.fFormat = pixelFormat; + imageDesc.fWidth = desc.fWidth; + imageDesc.fHeight = desc.fHeight; + imageDesc.fLevels = 1; + imageDesc.fSamples = 1; + imageDesc.fImageTiling = linearTiling ? VK_IMAGE_TILING_LINEAR : VK_IMAGE_TILING_OPTIMAL; + imageDesc.fUsageFlags = usageFlags; + imageDesc.fMemProps = memProps; + + GrVkTexture* tex; + if (renderTarget) { + tex = GrVkTextureRenderTarget::CreateNewTextureRenderTarget(this, desc, lifeCycle, + imageDesc); + } else { + tex = GrVkTexture::CreateNewTexture(this, desc, lifeCycle, imageDesc); + } + + if (!tex) { + return nullptr; + } + + if (srcData) { + if (!this->uploadTexData(tex, 0, 0, desc.fWidth, desc.fHeight, desc.fConfig, srcData, + rowBytes)) { + tex->unref(); + return nullptr; + } + } + + return tex; +} + +//////////////////////////////////////////////////////////////////////////////// + +static GrSurfaceOrigin resolve_origin(GrSurfaceOrigin origin) { + // By default, all textures in Vk use TopLeft + if (kDefault_GrSurfaceOrigin == origin) { + return kTopLeft_GrSurfaceOrigin; + } else { + return origin; + } +} + +GrTexture* GrVkGpu::onWrapBackendTexture(const GrBackendTextureDesc& desc, + GrWrapOwnership ownership) { + VkFormat format; + if (!GrPixelConfigToVkFormat(desc.fConfig, &format)) { + return nullptr; + } + + if (0 == desc.fTextureHandle) { + return nullptr; + } + + int maxSize = this->caps()->maxTextureSize(); + if (desc.fWidth > maxSize || desc.fHeight > maxSize) { + return nullptr; + } + + // TODO: determine what format Chrome will actually send us and turn it into a Resource + GrVkImage::Resource* imageRsrc = reinterpret_cast<GrVkImage::Resource*>(desc.fTextureHandle); + + GrGpuResource::LifeCycle lifeCycle; + switch (ownership) { + case kAdopt_GrWrapOwnership: + lifeCycle = GrGpuResource::kAdopted_LifeCycle; + break; + case kBorrow_GrWrapOwnership: + lifeCycle = GrGpuResource::kBorrowed_LifeCycle; + break; + } + + GrSurfaceDesc surfDesc; + // next line relies on GrBackendTextureDesc's flags matching GrTexture's + surfDesc.fFlags = (GrSurfaceFlags)desc.fFlags; + surfDesc.fWidth = desc.fWidth; + surfDesc.fHeight = desc.fHeight; + surfDesc.fConfig = desc.fConfig; + surfDesc.fSampleCnt = SkTMin(desc.fSampleCnt, this->caps()->maxSampleCount()); + bool renderTarget = SkToBool(desc.fFlags & kRenderTarget_GrBackendTextureFlag); + // In GL, Chrome assumes all textures are BottomLeft + // In VK, we don't have this restriction + surfDesc.fOrigin = resolve_origin(desc.fOrigin); + + GrVkTexture* texture = nullptr; + if (renderTarget) { + texture = GrVkTextureRenderTarget::CreateWrappedTextureRenderTarget(this, surfDesc, + lifeCycle, format, + imageRsrc); + } else { + texture = GrVkTexture::CreateWrappedTexture(this, surfDesc, lifeCycle, format, imageRsrc); + } + if (!texture) { + return nullptr; + } + + return texture; +} + +GrRenderTarget* GrVkGpu::onWrapBackendRenderTarget(const GrBackendRenderTargetDesc& wrapDesc, + GrWrapOwnership ownership) { + + // TODO: determine what format Chrome will actually send us and turn it into a Resource + GrVkImage::Resource* imageRsrc = + reinterpret_cast<GrVkImage::Resource*>(wrapDesc.fRenderTargetHandle); + + GrGpuResource::LifeCycle lifeCycle; + switch (ownership) { + case kAdopt_GrWrapOwnership: + lifeCycle = GrGpuResource::kAdopted_LifeCycle; + break; + case kBorrow_GrWrapOwnership: + lifeCycle = GrGpuResource::kBorrowed_LifeCycle; + break; + } + + GrSurfaceDesc desc; + desc.fConfig = wrapDesc.fConfig; + desc.fFlags = kCheckAllocation_GrSurfaceFlag; + desc.fWidth = wrapDesc.fWidth; + desc.fHeight = wrapDesc.fHeight; + desc.fSampleCnt = SkTMin(wrapDesc.fSampleCnt, this->caps()->maxSampleCount()); + + desc.fOrigin = resolve_origin(wrapDesc.fOrigin); + + GrVkRenderTarget* tgt = GrVkRenderTarget::CreateWrappedRenderTarget(this, desc, + lifeCycle, imageRsrc); + if (tgt && wrapDesc.fStencilBits) { + if (!createStencilAttachmentForRenderTarget(tgt, desc.fWidth, desc.fHeight)) { + tgt->unref(); + return nullptr; + } + } + return tgt; +} + +//////////////////////////////////////////////////////////////////////////////// + +void GrVkGpu::bindGeometry(const GrPrimitiveProcessor& primProc, + const GrNonInstancedVertices& vertices) { + GrVkVertexBuffer* vbuf; + vbuf = (GrVkVertexBuffer*)vertices.vertexBuffer(); + SkASSERT(vbuf); + SkASSERT(!vbuf->isMapped()); + + vbuf->addMemoryBarrier(this, + VK_ACCESS_HOST_WRITE_BIT, + VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT, + VK_PIPELINE_STAGE_HOST_BIT, + VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, + false); + + fCurrentCmdBuffer->bindVertexBuffer(this, vbuf); + + if (vertices.isIndexed()) { + GrVkIndexBuffer* ibuf = (GrVkIndexBuffer*)vertices.indexBuffer(); + SkASSERT(ibuf); + SkASSERT(!ibuf->isMapped()); + + ibuf->addMemoryBarrier(this, + VK_ACCESS_HOST_WRITE_BIT, + VK_ACCESS_INDEX_READ_BIT, + VK_PIPELINE_STAGE_HOST_BIT, + VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, + false); + + fCurrentCmdBuffer->bindIndexBuffer(this, ibuf); + } +} + +void GrVkGpu::buildProgramDesc(GrProgramDesc* desc, + const GrPrimitiveProcessor& primProc, + const GrPipeline& pipeline) const { + if (!GrVkProgramDescBuilder::Build(desc, primProc, pipeline, *this->vkCaps().glslCaps())) { + SkDEBUGFAIL("Failed to generate GL program descriptor"); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +GrStencilAttachment* GrVkGpu::createStencilAttachmentForRenderTarget(const GrRenderTarget* rt, + int width, + int height) { + SkASSERT(rt->asTexture()); + SkASSERT(width >= rt->width()); + SkASSERT(height >= rt->height()); + + int samples = rt->numStencilSamples(); + + SkASSERT(this->vkCaps().stencilFormats().count()); + const GrVkCaps::StencilFormat& sFmt = this->vkCaps().stencilFormats()[0]; + + GrVkStencilAttachment* stencil(GrVkStencilAttachment::Create(this, + GrGpuResource::kCached_LifeCycle, + width, + height, + samples, + sFmt)); + fStats.incStencilAttachmentCreates(); + return stencil; +} + +//////////////////////////////////////////////////////////////////////////////// + +GrBackendObject GrVkGpu::createTestingOnlyBackendTexture(void* srcData, int w, int h, + GrPixelConfig config) { + + VkFormat pixelFormat; + if (!GrPixelConfigToVkFormat(config, &pixelFormat)) { + return 0; + } + + bool linearTiling = false; + if (!fVkCaps->isConfigTexturable(config)) { + return 0; + } + + if (fVkCaps->isConfigTexurableLinearly(config)) { + linearTiling = true; + } + + // Currently this is not supported since it requires a copy which has not yet been implemented. + if (srcData && !linearTiling) { + return 0; + } + + VkImageUsageFlags usageFlags = VK_IMAGE_USAGE_SAMPLED_BIT; + usageFlags |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + usageFlags |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; + + VkFlags memProps = (srcData && linearTiling) ? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT : + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + + // This ImageDesc refers to the texture that will be read by the client. Thus even if msaa is + // requested, this ImageDesc describes the resolved texutre. Therefore we always have samples set + // to 1. + GrVkImage::ImageDesc imageDesc; + imageDesc.fImageType = VK_IMAGE_TYPE_2D; + imageDesc.fFormat = pixelFormat; + imageDesc.fWidth = w; + imageDesc.fHeight = h; + imageDesc.fLevels = 1; + imageDesc.fSamples = 1; + imageDesc.fImageTiling = linearTiling ? VK_IMAGE_TILING_LINEAR : VK_IMAGE_TILING_OPTIMAL; + imageDesc.fUsageFlags = usageFlags; + imageDesc.fMemProps = memProps; + + const GrVkImage::Resource* imageRsrc = GrVkImage::CreateResource(this, imageDesc); + if (!imageRsrc) { + return 0; + } + + if (srcData) { + if (linearTiling) { + const VkImageSubresource subres = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, // mipLevel + 0, // arraySlice + }; + VkSubresourceLayout layout; + VkResult err; + + const GrVkInterface* interface = this->vkInterface(); + + GR_VK_CALL(interface, GetImageSubresourceLayout(fDevice, + imageRsrc->fImage, + &subres, + &layout)); + + void* mapPtr; + err = GR_VK_CALL(interface, MapMemory(fDevice, + imageRsrc->fAlloc, + 0, + layout.rowPitch * h, + 0, + &mapPtr)); + if (err) { + imageRsrc->unref(this); + return 0; + } + + size_t bpp = GrBytesPerPixel(config); + size_t rowCopyBytes = bpp * w; + // If there is no padding on dst (layout.rowPitch) we can do a single memcopy. + // This assumes the srcData comes in with no padding. + if (rowCopyBytes == layout.rowPitch) { + memcpy(mapPtr, srcData, rowCopyBytes * h); + } else { + SkRectMemcpy(mapPtr, layout.rowPitch, srcData, w, rowCopyBytes, h); + } + GR_VK_CALL(interface, UnmapMemory(fDevice, imageRsrc->fAlloc)); + } else { + // TODO: Add support for copying to optimal tiling + SkASSERT(false); + } + } + + return (GrBackendObject)imageRsrc; +} + +bool GrVkGpu::isTestingOnlyBackendTexture(GrBackendObject id) const { + GrVkImage::Resource* backend = reinterpret_cast<GrVkImage::Resource*>(id); + + if (backend && backend->fImage && backend->fAlloc) { + VkMemoryRequirements req; + memset(&req, 0, sizeof(req)); + GR_VK_CALL(this->vkInterface(), GetImageMemoryRequirements(fDevice, + backend->fImage, + &req)); + // TODO: find a better check + // This will probably fail with a different driver + return (req.size > 0) && (req.size <= 8192 * 8192); + } + + return false; +} + +void GrVkGpu::deleteTestingOnlyBackendTexture(GrBackendObject id, bool abandon) { + GrVkImage::Resource* backend = reinterpret_cast<GrVkImage::Resource*>(id); + + if (backend) { + if (!abandon) { + backend->unref(this); + } else { + backend->unrefAndAbandon(); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +void GrVkGpu::addMemoryBarrier(VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + bool byRegion, + VkMemoryBarrier* barrier) const { + SkASSERT(fCurrentCmdBuffer); + fCurrentCmdBuffer->pipelineBarrier(this, + srcStageMask, + dstStageMask, + byRegion, + GrVkCommandBuffer::kMemory_BarrierType, + barrier); +} + +void GrVkGpu::addBufferMemoryBarrier(VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + bool byRegion, + VkBufferMemoryBarrier* barrier) const { + SkASSERT(fCurrentCmdBuffer); + fCurrentCmdBuffer->pipelineBarrier(this, + srcStageMask, + dstStageMask, + byRegion, + GrVkCommandBuffer::kBufferMemory_BarrierType, + barrier); +} + +void GrVkGpu::addImageMemoryBarrier(VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + bool byRegion, + VkImageMemoryBarrier* barrier) const { + SkASSERT(fCurrentCmdBuffer); + fCurrentCmdBuffer->pipelineBarrier(this, + srcStageMask, + dstStageMask, + byRegion, + GrVkCommandBuffer::kImageMemory_BarrierType, + barrier); +} + +void GrVkGpu::finishDrawTarget() { + // Submit the current command buffer to the Queue + this->submitCommandBuffer(kSkip_SyncQueue); +} + +void GrVkGpu::onClear(GrRenderTarget* target, const SkIRect& rect, GrColor color) { + // parent class should never let us get here with no RT + SkASSERT(target); + + VkClearColorValue vkColor; + GrColorToRGBAFloat(color, vkColor.float32); + + GrVkRenderTarget* vkRT = static_cast<GrVkRenderTarget*>(target); + VkImageLayout origDstLayout = vkRT->currentLayout(); + + if (rect.width() != target->width() || rect.height() != target->height()) { + VkAccessFlags srcAccessMask = GrVkMemory::LayoutToSrcAccessMask(origDstLayout); + VkAccessFlags dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + VkPipelineStageFlags srcStageMask = + GrVkMemory::LayoutToPipelineStageFlags(vkRT->currentLayout()); + VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + vkRT->setImageLayout(this, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + srcAccessMask, + dstAccessMask, + srcStageMask, + dstStageMask, + false); + + VkClearRect clearRect; + clearRect.rect.offset = { rect.fLeft, rect.fTop }; + clearRect.rect.extent = { (uint32_t)rect.width(), (uint32_t)rect.height() }; + clearRect.baseArrayLayer = 0; + clearRect.layerCount = 1; + + + + const GrVkRenderPass* renderPass = vkRT->simpleRenderPass(); + SkASSERT(renderPass); + fCurrentCmdBuffer->beginRenderPass(this, renderPass, *vkRT); + + uint32_t colorIndex; + SkAssertResult(renderPass->colorAttachmentIndex(&colorIndex)); + + VkClearAttachment attachment; + attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + attachment.colorAttachment = colorIndex; + attachment.clearValue.color = vkColor; + + fCurrentCmdBuffer->clearAttachments(this, 1, &attachment, 1, &clearRect); + fCurrentCmdBuffer->endRenderPass(this); + return; + } + + VkPipelineStageFlags srcStageMask = GrVkMemory::LayoutToPipelineStageFlags(origDstLayout); + VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; + + VkAccessFlags srcAccessMask = GrVkMemory::LayoutToSrcAccessMask(origDstLayout);; + VkAccessFlags dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + vkRT->setImageLayout(this, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + srcAccessMask, + dstAccessMask, + srcStageMask, + dstStageMask, + false); + + + VkImageSubresourceRange subRange; + memset(&subRange, 0, sizeof(VkImageSubresourceRange)); + subRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + subRange.baseMipLevel = 0; + subRange.levelCount = 1; + subRange.baseArrayLayer = 0; + subRange.layerCount = 1; + + // In the future we may not actually be doing this type of clear at all. If we are inside a + // render pass or doing a non full clear then we will use CmdClearColorAttachment. The more + // common use case will be clearing an attachment at the start of a render pass, in which case + // we will use the clear load ops. + fCurrentCmdBuffer->clearColorImage(this, + vkRT, + &vkColor, + 1, &subRange); +} + +inline bool can_copy_image(const GrSurface* dst, + const GrSurface* src, + const GrVkGpu* gpu) { + if (src->asTexture() && + dst->asTexture() && + src->origin() == dst->origin() && + src->config() == dst->config()) { + return true; + } + + // How does msaa play into this? If a VkTexture is multisampled, are we copying the multisampled + // or the resolved image here? + + return false; +} + +void GrVkGpu::copySurfaceAsCopyImage(GrSurface* dst, + GrSurface* src, + const SkIRect& srcRect, + const SkIPoint& dstPoint) { + SkASSERT(can_copy_image(dst, src, this)); + + // Insert memory barriers to switch src and dst to transfer_source and transfer_dst layouts + GrVkTexture* dstTex = static_cast<GrVkTexture*>(dst->asTexture()); + GrVkTexture* srcTex = static_cast<GrVkTexture*>(src->asTexture()); + + VkImageLayout origDstLayout = dstTex->currentLayout(); + VkImageLayout origSrcLayout = srcTex->currentLayout(); + + VkPipelineStageFlags srcStageMask = GrVkMemory::LayoutToPipelineStageFlags(origDstLayout); + VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; + + // These flags are for flushing/invalidating caches and for the dst image it doesn't matter if + // the cache is flushed since it is only being written to. + VkAccessFlags srcAccessMask = GrVkMemory::LayoutToSrcAccessMask(origDstLayout);; + VkAccessFlags dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + dstTex->setImageLayout(this, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + srcAccessMask, + dstAccessMask, + srcStageMask, + dstStageMask, + false); + + srcStageMask = GrVkMemory::LayoutToPipelineStageFlags(origSrcLayout); + dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; + + srcAccessMask = GrVkMemory::LayoutToSrcAccessMask(origSrcLayout); + dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + + srcTex->setImageLayout(this, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + srcAccessMask, + dstAccessMask, + srcStageMask, + dstStageMask, + false); + + // Flip rect if necessary + SkIRect srcVkRect = srcRect; + int32_t dstY = dstPoint.fY; + + if (kBottomLeft_GrSurfaceOrigin == src->origin()) { + SkASSERT(kBottomLeft_GrSurfaceOrigin == dst->origin()); + srcVkRect.fTop = src->height() - srcRect.fBottom; + srcVkRect.fBottom = src->height() - srcRect.fTop; + dstY = dst->height() - dstPoint.fY - srcVkRect.height(); + } + + VkImageCopy copyRegion; + memset(©Region, 0, sizeof(VkImageCopy)); + copyRegion.srcSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }; + copyRegion.srcOffset = { srcVkRect.fLeft, srcVkRect.fTop, 0 }; + copyRegion.dstSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }; + copyRegion.dstOffset = { dstPoint.fX, dstY, 0 }; + copyRegion.extent = { (uint32_t)srcVkRect.width(), (uint32_t)srcVkRect.height(), 0 }; + + fCurrentCmdBuffer->copyImage(this, + srcTex, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + dstTex, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ©Region); +} + +inline bool can_copy_as_draw(const GrSurface* dst, + const GrSurface* src, + const GrVkGpu* gpu) { + return false; +} + +void GrVkGpu::copySurfaceAsDraw(GrSurface* dst, + GrSurface* src, + const SkIRect& srcRect, + const SkIPoint& dstPoint) { + SkASSERT(false); +} + +bool GrVkGpu::onCopySurface(GrSurface* dst, + GrSurface* src, + const SkIRect& srcRect, + const SkIPoint& dstPoint) { + if (can_copy_image(dst, src, this)) { + this->copySurfaceAsCopyImage(dst, src, srcRect, dstPoint); + return true; + } + + if (can_copy_as_draw(dst, src, this)) { + this->copySurfaceAsDraw(dst, src, srcRect, dstPoint); + return true; + } + + return false; +} + +bool GrVkGpu::onGetReadPixelsInfo(GrSurface* srcSurface, int width, int height, size_t rowBytes, + GrPixelConfig readConfig, DrawPreference* drawPreference, + ReadPixelTempDrawInfo* tempDrawInfo) { + // Currently we don't handle draws, so if the caller wants/needs to do a draw we need to fail + if (kNoDraw_DrawPreference != *drawPreference) { + return false; + } + + if (srcSurface->config() != readConfig) { + // TODO: This should fall back to drawing or copying to change config of srcSurface to match + // that of readConfig. + return false; + } + + return true; +} + +bool GrVkGpu::onReadPixels(GrSurface* surface, + int left, int top, int width, int height, + GrPixelConfig config, + void* buffer, + size_t rowBytes) { + VkFormat pixelFormat; + if (!GrPixelConfigToVkFormat(config, &pixelFormat)) { + return false; + } + + GrVkTexture* tgt = static_cast<GrVkTexture*>(surface->asTexture()); + if (!tgt) { + return false; + } + + // Change layout of our target so it can be used as copy + VkImageLayout layout = tgt->currentLayout(); + VkPipelineStageFlags srcStageMask = GrVkMemory::LayoutToPipelineStageFlags(layout); + VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; + VkAccessFlags srcAccessMask = GrVkMemory::LayoutToSrcAccessMask(layout); + VkAccessFlags dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + tgt->setImageLayout(this, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + srcAccessMask, + dstAccessMask, + srcStageMask, + dstStageMask, + false); + + GrVkTransferBuffer* transferBuffer = + reinterpret_cast<GrVkTransferBuffer*>(this->createTransferBuffer(rowBytes * height, + kGpuToCpu_TransferType)); + + bool flipY = kBottomLeft_GrSurfaceOrigin == surface->origin(); + VkOffset3D offset = { + left, + flipY ? surface->height() - top - height : top, + 0 + }; + + // Copy the image to a buffer so we can map it to cpu memory + VkBufferImageCopy region; + memset(®ion, 0, sizeof(VkBufferImageCopy)); + region.bufferOffset = 0; + region.bufferRowLength = 0; // Forces RowLength to be imageExtent.width + region.bufferImageHeight = 0; // Forces height to be tightly packed. Only useful for 3d images. + region.imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }; + region.imageOffset = offset; + region.imageExtent = { (uint32_t)width, (uint32_t)height, 1 }; + + fCurrentCmdBuffer->copyImageToBuffer(this, + tgt, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + transferBuffer, + 1, + ®ion); + + // make sure the copy to buffer has finished + transferBuffer->addMemoryBarrier(this, + VK_ACCESS_TRANSFER_WRITE_BIT, + VK_ACCESS_HOST_READ_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_HOST_BIT, + false); + + // We need to submit the current command buffer to the Queue and make sure it finishes before + // we can copy the data out of the buffer. + this->submitCommandBuffer(kForce_SyncQueue); + + void* mappedMemory = transferBuffer->map(); + + memcpy(buffer, mappedMemory, rowBytes*height); + + transferBuffer->unmap(); + transferBuffer->unref(); + + if (flipY) { + SkAutoSMalloc<32 * sizeof(GrColor)> scratch; + size_t tightRowBytes = GrBytesPerPixel(config) * width; + scratch.reset(tightRowBytes); + void* tmpRow = scratch.get(); + // flip y in-place by rows + const int halfY = height >> 1; + char* top = reinterpret_cast<char*>(buffer); + char* bottom = top + (height - 1) * rowBytes; + for (int y = 0; y < halfY; y++) { + memcpy(tmpRow, top, tightRowBytes); + memcpy(top, bottom, tightRowBytes); + memcpy(bottom, tmpRow, tightRowBytes); + top += rowBytes; + bottom -= rowBytes; + } + } + + return true; +} + +void GrVkGpu::onDraw(const DrawArgs& args, const GrNonInstancedVertices& vertices) { + GrRenderTarget* rt = args.fPipeline->getRenderTarget(); + GrVkRenderTarget* vkRT = static_cast<GrVkRenderTarget*>(rt); + const GrVkRenderPass* renderPass = vkRT->simpleRenderPass(); + SkASSERT(renderPass); + + + GrVkProgram* program = GrVkProgramBuilder::CreateProgram(this, args, + vertices.primitiveType(), + *renderPass); + + if (!program) { + return; + } + + program->setData(this, *args.fPrimitiveProcessor, *args.fPipeline); + + fCurrentCmdBuffer->beginRenderPass(this, renderPass, *vkRT); + + program->bind(this, fCurrentCmdBuffer); + + this->bindGeometry(*args.fPrimitiveProcessor, vertices); + + // Change layout of our render target so it can be used as the color attachment + VkImageLayout layout = vkRT->currentLayout(); + // Our color attachment is purely a destination and won't be read so don't need to flush or + // invalidate any caches + VkPipelineStageFlags srcStageMask = GrVkMemory::LayoutToPipelineStageFlags(layout); + VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags srcAccessMask = GrVkMemory::LayoutToSrcAccessMask(layout); + VkAccessFlags dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + vkRT->setImageLayout(this, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + srcAccessMask, + dstAccessMask, + srcStageMask, + dstStageMask, + false); + + if (vertices.isIndexed()) { + fCurrentCmdBuffer->drawIndexed(this, + vertices.indexCount(), + 1, + vertices.startIndex(), + vertices.startVertex(), + 0); + } else { + fCurrentCmdBuffer->draw(this, vertices.vertexCount(), 1, vertices.startVertex(), 0); + } + + fCurrentCmdBuffer->endRenderPass(this); + + // Technically we don't have to call this here (since there is a safety check in program:setData + // but this will allow for quicker freeing of resources if the program sits in a cache for a + // while. + program->freeTempResources(this); + // This free will go away once we setup a program cache, and then the cache will be responsible + // for call freeGpuResources. + program->freeGPUResources(this); + program->unref(); + +#if SWAP_PER_DRAW + glFlush(); +#if defined(SK_BUILD_FOR_MAC) + aglSwapBuffers(aglGetCurrentContext()); + int set_a_break_pt_here = 9; + aglSwapBuffers(aglGetCurrentContext()); +#elif defined(SK_BUILD_FOR_WIN32) + SwapBuf(); + int set_a_break_pt_here = 9; + SwapBuf(); +#endif +#endif +} + |