haikuwebkit/Tools/WebGPUAPIStructure/WebGPU.sln

58 lines
3.2 KiB
Plaintext
Raw Permalink Normal View History

Demonstrate a possible structure of the WebGPU API https://bugs.webkit.org/show_bug.cgi?id=178874 Reviewed by Dean Jackson. Over the past few weeks, we've been putting together an example showing that a WebGPU API which has implicit barriers can work on all three low-level graphics APIs. We've implemented it on top of Vulkan first, because this is the API which has the strictest requirements and is most difficult to use. With this API, this is a valid WebGPU snippet: auto device = WebGPU::Device::create(hInstance, hWnd); auto& commandQueue = device->getCommandQueue(); auto& renderState = device->getRenderState(vertexShader, "main", fragmentShader, "main", { }, { }, { }, nullptr); … later, in the draw() function … auto renderPass = commandQueue->createRenderPass(nullptr); renderPass->setRenderState(renderState); renderPass->setViewport(0, 0, width, height); renderPass->setScissorRect(0, 0, width, height); renderPass->draw(3); commandQueue->commitRenderPass(std::move(renderPass)); commandQueue->present(); This snippet doesn’t hook up any vertex attributes or resources, which means the vertex shader has to say something like ({vec4(…), vec4(…), vec4(…)})[gl_VertexIndex]. It also passes in “nullptr” when creating the render pass, which means “render to the screen, rather than to a frame buffer.” You can also see that it doesn’t attach any resources to the draw call. In Direct3D 12 and Vulkan, resources are bound in sets, rather than individually. For example, a set might contain two uniform buffers, a texture, and another uniform buffer. At draw time, you swap in whole sets of resources with a single call. A shader invocation can access a collection of sets. Because all shader resource accesses are indirected through these sets, the shape of these sets needs to be supplied at the time you compile the render state. Here is a snippet which bounds a single set which contains a uniform buffer and a texture: auto buffer = device->getBuffer(bufferInitialContents); auto texture = device->getTexture(buffer width, height, WebGPU::PixelFormat::RGBA8, textureInitialContents); // One resource set, which holds a single uniform buffer object and a single texture auto& renderState = device->getRenderState(vertexShader, "main", fragmentShader, "main", { }, { }, { { WebGPU::ResourceType::UniformBufferObject, WebGPU::ResourceType::Texture } }, nullptr); … later, in the draw() function … auto renderPass = commandQueue->createRenderPass(nullptr); renderPass->setRenderState(renderState); renderPass->setResources(0, { WebGPU::UniformBufferObjectReference(buffer.get()), WebGPU::TextureReference(texture.get()) }); … renderPass->draw(3); commandQueue->commitRenderPass(std::move(renderPass)); commandQueue->present(); The first argument to the setResources() call identifies which set to populate with the supplied resources. One tenant of the low-level graphics APIs is that, if you’ve enabled double buffering (or triple buffering), the GPU is executing one frame at the same time you are recording the next frame. This often means that you need duplicate resources so the CPU and GPU don’t step on each other’s toes. However, platforms have platform-specific requirements about whether or not they can be double / triple buffered, and we don’t want to expose this to the Web for fear of badly-authored programs. To solve this, resources are reference counted, and the return type of getBuffer() is an RAII type called BufferHolder which increments and decrements the reference count automatically. The reference count is also incremented and decremented when the GPU is using the resource in a Pass. When the reference count reaches 0, the resource isn’t destroyed; instead, it’s simply moved to a “free list” which getBuffer() may pull from. Therefore, applications don’t need to know whether the frame buffer is double buffered or triple buffered; they can just getBuffer() each frame, and the correct number of buffers will be created and recycled. { auto buffer = device->getBuffer(bufferSize); // These get recycled … populate the buffer … auto renderPass = commandQueue->createRenderPass(nullptr); renderPass->setRenderState(renderState); renderPass->setResources(0, { WebGPU::UniformBufferObjectReference(buffer.get()) }); … renderPass->draw(…); commandQueue->commitRenderPass(std::move(renderPass)); } commandQueue->present(); In Direct3D and Vulkan, vertex buffers and index buffers are not part of the resource sets mentioned above. Instead, you tell the render state about the shape of the vertex and index buffers, and you swap them out independently in the draw loop. Metal and Vulkan have almost identical API to specify this shape of the vertex buffers, so I’ve mostly copied it. In this example, we have two vertex attributes, a vec2 and a vec3, which both come from the same buffer: // { Attribute format, offset within stride, buffer to pull from } std::vector<WebGPU::RenderState::VertexAttribute> vertexAttributes = { {WebGPU::RenderState::VertexFormat::Float2, 0, 0}, {WebGPU::RenderState::VertexFormat::Float3, sizeof(float) * 2, 0} }; // A single vertex buffer, with a stride of 5 floats auto& renderState = device->getRenderState(vertexShader, "main", fragmentShader, "main", { sizeof(float) * 5 }, vertexAttributes, resourceTypes, nullptr); … later, in the draw() function … auto renderPass = commandQueue->createRenderPass(nullptr); renderPass->setRenderState(renderState); renderPass->setVertexAttributeBuffers({ vertexBuffer.get() }); // The one vertex buffer which both attributes pull from renderPass->setResources(…); … renderPass->draw(…); commandQueue->commitRenderPass(std::move(renderPass)); commandQueue->present(); You can also tell the RenderState about how many render targets you have and their formats, and then when you create the RenderPass, you specify the specific textures you want to render into. std::vector<WebGPU::PixelFormat> colorPixelFormats = { WebGPU::PixelFormat::RGBA8, WebGPU::PixelFormat::RGBA8 }; // Two render targets, with these formats auto& renderState = device->getRenderState(vertexShader, "main", fragmentShader, "main", vertexBufferStrides, vertexAttributes, resourceTypes, &colorPixelFormats); … later, in the draw() function … std::vector<std::reference_wrapper<WebGPU::Texture>> destinationTextures = { texture1->get(), texture2->get() }; auto renderPass = commandQueue->createRenderPass(&destinationTextures); renderPass->setRenderState(renderState); … renderPass->draw(…); commandQueue->commitRenderPass(std::move(renderPass)); // Now, draw one of the textures to the screen. Note that no synchronization is necessary here! auto renderPass = commandQueue->createRenderPass(nullptr); renderPass->setRenderState(renderState2); renderPass->setResources(0, { WebGPU:: TextureReference(texture1.get()) }); … renderPass->draw(…); commandQueue->commitRenderPass(std::move(renderPass)); commandQueue->present(); Just like how in Metal has Render Encoders and Compute Encoders, WebGPU has RenderPasses and ComputePasses. auto& computeState = device->getComputeState(computeShader, "main", resourceTypes); … auto computePass = commandQueue->createComputePass(); computePass->setComputeState(computeState); computePass->setResources(0, resources); computePass->dispatch(width, height, depth); commandQueue->commitComputePass(std::move(computePass)); // Now, draw the resources we just computed. Note that no synchronization is necessary here! auto renderPass = commandQueue->createRenderPass(nullptr); renderPass->setRenderState(renderState); renderPass->setResources(0, resources }); … renderPass->draw(…); commandQueue->commitRenderPass(std::move(renderPass)); commandQueue->present(); There are also two other types of passes: one that corresponds to a Metal blit encoder, and one that allows the CPU to change the contents of GPU buffers and textures. This last kind of pass is a little interesting: you can’t just change the contents of a buffer at any time you feel like it, because that resource might be in use by the GPU. Therefore, we need to do the same kind of synchronization that we already do at render pass boundaries. In addition, both Vulkan and Direct3D have a concept of a memory heap. A resource might exist inside a heap which is fast, but invisible from the CPU, or in a heap which is slow, but visible by the CPU. Certain operations are not possible from some types of images (e.g. non-tiled textures may not be able to be sampled from). The usual way to get around this problem is to have two resources: a slow staging resource which the CPU can see, and a fast resource which the CPU can’t see. Uploading data is a two-pass algorithm, where the CPU memcpy()s into the slow staging resource, and then a blit command is enqueued on the GPU to copy the contents of the staging resource to the real resource. This requires that the upload have access to the commandQueue so it can possibly enqueue a blit between the staging and real resources. Therefore, a pass is the right level of abstraction for these facilities. std::queue<boost::unique_future<std::vector<uint8_t>>> futureQueue; // Promises for data downloads from the GPU … later, in the draw() function … // See if any of the previously-enqueued downloads are finished while (!futureQueue.empty() && futureQueue.front(). has_value()) { std::vector<uint8_t>& data = futureQueue.front().get(); // Use the downloaded data futureQueue.pop(); } … auto hostAccessPass = commandQueue->createHostAccessPass(); hostAccessPass->overwriteBuffer(buffer->get(), bufferContents); // Upload data to a resource futureQueue.emplace(hostAccessPass->getBufferContents(buffer->get())); commandQueue->commitHostAccessPass(std::move(hostAccessPass)); You can also issue copy commands between resources entirely on the GPU: auto blitPass = commandQueue->createBlitPass(); blitPass->copyTexture(source->get(), destination->get(), sourceX, sourceY, destinationX, destinationY, width, height); commandQueue->commitBlitPass(std::move(blitPass)); * Scripts/webkitpy/style/checker.py: * WebGPUAPIStructure/Example/Example.cpp: Added. (readFile): (drawWebGPU): (wWinMain): (MyRegisterClass): (InitInstance): (WndProc): * WebGPUAPIStructure/Example/Example.h: Added. * WebGPUAPIStructure/Example/Example.ico: Added. * WebGPUAPIStructure/Example/Example.rc: Added. * WebGPUAPIStructure/Example/Example.vcxproj: Added. * WebGPUAPIStructure/Example/Example.vcxproj.filters: Added. * WebGPUAPIStructure/Example/Example.vcxproj.user: Added. * WebGPUAPIStructure/Example/resource.h: Added. * WebGPUAPIStructure/Example/small.ico: Added. * WebGPUAPIStructure/Example/stdafx.cpp: Added. * WebGPUAPIStructure/Example/stdafx.h: Added. * WebGPUAPIStructure/Example/targetver.h: Added. * WebGPUAPIStructure/WebGPU-Common/WebGPU-Common.vcxproj: Added. * WebGPUAPIStructure/WebGPU-Common/WebGPU-Common.vcxproj.filters: Added. * WebGPUAPIStructure/WebGPU-Common/WebGPU.cpp: Added. (WebGPU::BufferHolder::BufferHolder): (WebGPU::BufferHolder::~BufferHolder): (WebGPU::TextureHolder::TextureHolder): (WebGPU::TextureHolder::~TextureHolder): (WebGPU::SamplerHolder::SamplerHolder): (WebGPU::SamplerHolder::~SamplerHolder): * WebGPUAPIStructure/WebGPU-Common/WebGPU.h: Added. (WebGPU::Queue::~Queue): (WebGPU::RenderState::~RenderState): (WebGPU::ComputeState::~ComputeState): (WebGPU::Buffer::~Buffer): (WebGPU::Texture::~Texture): (WebGPU::Sampler::~Sampler): (WebGPU::TextureReference::TextureReference): (WebGPU::TextureReference::get const): (WebGPU::SamplerReference::SamplerReference): (WebGPU::SamplerReference::get const): (WebGPU::UniformBufferObjectReference::UniformBufferObjectReference): (WebGPU::UniformBufferObjectReference::get const): (WebGPU::ShaderStorageBufferObjectReference::ShaderStorageBufferObjectReference): (WebGPU::ShaderStorageBufferObjectReference::get const): (WebGPU::RenderPass::~RenderPass): (WebGPU::ComputePass::~ComputePass): (WebGPU::BlitPass::~BlitPass): (WebGPU::HostAccessPass::~HostAccessPass): (WebGPU::BufferHolder::get): (WebGPU::TextureHolder::get): (WebGPU::SamplerHolder::get): (WebGPU::Device::~Device): * WebGPUAPIStructure/WebGPU-Vulkan/BlitPassImpl.cpp: Added. (WebGPU::BlitPassImpl::BlitPassImpl): (WebGPU::BlitPassImpl::copyTexture): * WebGPUAPIStructure/WebGPU-Vulkan/BlitPassImpl.h: Added. * WebGPUAPIStructure/WebGPU-Vulkan/BufferImpl.cpp: Added. (WebGPU::BufferImpl::BufferImpl): (WebGPU::BufferImpl::decrementReferenceCount): * WebGPUAPIStructure/WebGPU-Vulkan/BufferImpl.h: Added. (WebGPU::BufferImpl::getBuffer const): (WebGPU::BufferImpl::getDeviceMemory const): (WebGPU::BufferImpl::incrementReferenceCount): (WebGPU::BufferImpl::getLength const): * WebGPUAPIStructure/WebGPU-Vulkan/ComputePassImpl.cpp: Added. (WebGPU::ComputePassImpl::ComputePassImpl): (WebGPU::ComputePassImpl::setComputeState): (WebGPU::ComputePassImpl::setResources): (WebGPU::ComputePassImpl::dispatch): * WebGPUAPIStructure/WebGPU-Vulkan/ComputePassImpl.h: Added. * WebGPUAPIStructure/WebGPU-Vulkan/ComputeStateImpl.cpp: Added. (WebGPU::ComputeStateImpl::ComputeStateImpl): * WebGPUAPIStructure/WebGPU-Vulkan/ComputeStateImpl.h: Added. (WebGPU::ComputeStateImpl::getPipeline const): (WebGPU::ComputeStateImpl::getPipelineLayout const): (WebGPU::ComputeStateImpl::getDescriptorSetLayouts const): * WebGPUAPIStructure/WebGPU-Vulkan/DeviceImpl.cpp: Added. (WebGPU::Device::create): (WebGPU::convertPixelFormat): (WebGPU::convertFormat): (WebGPU::debugReport): (WebGPU::DeviceImpl::DeviceImpl): (WebGPU::DeviceImpl::getCommandQueue): (WebGPU::DeviceImpl::prepareShader): (WebGPU::DeviceImpl::createPipelineLayout): (WebGPU::DeviceImpl::createCompatibleRenderPass): (WebGPU::convertVertexFormat): (WebGPU::DeviceImpl::getRenderState): (WebGPU::DeviceImpl::getComputeState): (WebGPU::DeviceImpl::getBuffer): (WebGPU::DeviceImpl::returnBuffer): (WebGPU::DeviceImpl::getTexture): (WebGPU::DeviceImpl::returnTexture): (WebGPU::DeviceImpl::getSampler): (WebGPU::DeviceImpl::returnSampler): (WebGPU::DeviceImpl::~DeviceImpl): * WebGPUAPIStructure/WebGPU-Vulkan/DeviceImpl.h: Added. (WebGPU::DeviceImpl::UniqueDebugReportCallbackEXT::UniqueDebugReportCallbackEXT): (WebGPU::DeviceImpl::UniqueDebugReportCallbackEXT::operator=): (WebGPU::DeviceImpl::UniqueDebugReportCallbackEXT::~UniqueDebugReportCallbackEXT): (WebGPU::DeviceImpl::UniqueDebugReportCallbackEXT::destroy): (WebGPU::DeviceImpl::TextureParameters::operator== const): (WebGPU::DeviceImpl::TextureParametersHash::operator() const): * WebGPUAPIStructure/WebGPU-Vulkan/HostAccessPassImpl.cpp: Added. (WebGPU::HostAccessPassImpl::HostAccessPassImpl): (WebGPU::HostAccessPassImpl::overwriteBuffer): (WebGPU::HostAccessPassImpl::getBufferContents): (WebGPU::HostAccessPassImpl::execute): * WebGPUAPIStructure/WebGPU-Vulkan/HostAccessPassImpl.h: Added. (WebGPU::HostAccessPassImpl::getFinishedEvent const): * WebGPUAPIStructure/WebGPU-Vulkan/PassImpl.cpp: Added. (WebGPU::PassImpl::PassImpl): (WebGPU::ResourceVisitor::operator()): (WebGPU::ResourceVisitor::getBindings const): (WebGPU::ResourceVisitor::releaseWriteDescriptorSets): (WebGPU::ResourceVisitor::getDescriptorImageInfos const): (WebGPU::ResourceVisitor::getDescriptorBufferInfos const): (WebGPU::ResourceVisitor::getBuffers const): (WebGPU::ResourceVisitor::getTextures const): (WebGPU::ResourceVisitor::getSamplers const): (WebGPU::ResourceVisitor::getImageCount const): (WebGPU::ResourceVisitor::getSamplerCount const): (WebGPU::ResourceVisitor::getUniformBufferCount const): (WebGPU::ResourceVisitor::getStorageBufferCount const): (WebGPU::PassImpl::setResources): (WebGPU::PassImpl::insertBuffer): (WebGPU::PassImpl::insertTexture): (WebGPU::PassImpl::insertSampler): * WebGPUAPIStructure/WebGPU-Vulkan/PassImpl.h: Added. (WebGPU::PassImpl::getCommandBuffer const): (WebGPU::PassImpl::iterateBuffers): (WebGPU::PassImpl::iterateTextures): (WebGPU::PassImpl::ResourceReference::ResourceReference): (WebGPU::PassImpl::ResourceReference::~ResourceReference): (WebGPU::PassImpl::ResourceReference::operator=): (WebGPU::PassImpl::ResourceReference::operator== const): (WebGPU::PassImpl::ResourceReference::get const): (WebGPU::PassImpl::ResourceReference::release): (WebGPU::PassImpl::ResourceReferenceHash::operator() const): * WebGPUAPIStructure/WebGPU-Vulkan/QueueImpl.cpp: Added. (WebGPU::QueueImpl::QueueImpl): (WebGPU::QueueImpl::prepareCurrentFrame): (WebGPU::QueueImpl::createSpecificRenderPass): (WebGPU::QueueImpl::createFramebuffer): (WebGPU::QueueImpl::createRenderPass): (WebGPU::QueueImpl::commitRenderPass): (WebGPU::QueueImpl::createComputePass): (WebGPU::QueueImpl::commitComputePass): (WebGPU::QueueImpl::createBlitPass): (WebGPU::QueueImpl::commitBlitPass): (WebGPU::QueueImpl::createHostAccessPass): (WebGPU::QueueImpl::commitHostAccessPass): (WebGPU::QueueImpl::present): (WebGPU::QueueImpl::commitPass): (WebGPU::QueueImpl::synchronizeResources): (WebGPU::QueueImpl::~QueueImpl): * WebGPUAPIStructure/WebGPU-Vulkan/QueueImpl.h: Added. * WebGPUAPIStructure/WebGPU-Vulkan/RenderPassImpl.cpp: Added. (WebGPU::RenderPassImpl::RenderPassImpl): (WebGPU::RenderPassImpl::setRenderState): (WebGPU::RenderPassImpl::setVertexAttributeBuffers): (WebGPU::RenderPassImpl::setResources): (WebGPU::RenderPassImpl::setViewport): (WebGPU::RenderPassImpl::setScissorRect): (WebGPU::RenderPassImpl::draw): * WebGPUAPIStructure/WebGPU-Vulkan/RenderPassImpl.h: Added. * WebGPUAPIStructure/WebGPU-Vulkan/RenderStateImpl.cpp: Added. (WebGPU::RenderStateImpl::RenderStateImpl): * WebGPUAPIStructure/WebGPU-Vulkan/RenderStateImpl.h: Added. (WebGPU::RenderStateImpl::getPipeline const): (WebGPU::RenderStateImpl::getPipelineLayout const): (WebGPU::RenderStateImpl::getDescriptorSetLayouts const): * WebGPUAPIStructure/WebGPU-Vulkan/SamplerImpl.cpp: Added. (WebGPU::SamplerImpl::SamplerImpl): (WebGPU::SamplerImpl::decrementReferenceCount): * WebGPUAPIStructure/WebGPU-Vulkan/SamplerImpl.h: Added. (WebGPU::SamplerImpl::getSampler): (WebGPU::SamplerImpl::incrementReferenceCount): (WebGPU::SamplerImpl::getFilter): (WebGPU::SamplerImpl::getMipmapMode): (WebGPU::SamplerImpl::getAddressMode): * WebGPUAPIStructure/WebGPU-Vulkan/TextureImpl.cpp: Added. (WebGPU::TextureImpl::TextureImpl): (WebGPU::TextureImpl::decrementReferenceCount): * WebGPUAPIStructure/WebGPU-Vulkan/TextureImpl.h: Added. (WebGPU::TextureImpl::getImage const): (WebGPU::TextureImpl::getImageView const): (WebGPU::TextureImpl::getFormat const): (WebGPU::TextureImpl::incrementReferenceCount): (WebGPU::TextureImpl::getWidth const): (WebGPU::TextureImpl::getHeight const): (WebGPU::TextureImpl::getTransferredToGPU const): (WebGPU::TextureImpl::setTransferredToGPU): * WebGPUAPIStructure/WebGPU-Vulkan/WebGPU-Vulkan.vcxproj: Added. * WebGPUAPIStructure/WebGPU-Vulkan/WebGPU-Vulkan.vcxproj.filters: Added. * WebGPUAPIStructure/WebGPU.sln: Added. Canonical link: https://commits.webkit.org/195053@main git-svn-id: https://svn.webkit.org/repository/webkit/trunk@224065 268f45cc-cd09-0410-ab3c-d52691b4dbfc
2017-10-26 23:32:10 +00:00

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27004.2005
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Example", "Example\Example.vcxproj", "{5817155C-AB74-445D-9A61-47CDE42C2BAD}"
ProjectSection(ProjectDependencies) = postProject
{9B2EFA45-D6F5-436B-8DFA-A675D1FECD5D} = {9B2EFA45-D6F5-436B-8DFA-A675D1FECD5D}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WebGPU-Common", "WebGPU-Common\WebGPU-Common.vcxproj", "{9E765542-8C51-43B4-B0C0-C166D21CB222}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WebGPU-Vulkan", "WebGPU-Vulkan\WebGPU-Vulkan.vcxproj", "{9B2EFA45-D6F5-436B-8DFA-A675D1FECD5D}"
ProjectSection(ProjectDependencies) = postProject
{9E765542-8C51-43B4-B0C0-C166D21CB222} = {9E765542-8C51-43B4-B0C0-C166D21CB222}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5817155C-AB74-445D-9A61-47CDE42C2BAD}.Debug|x64.ActiveCfg = Debug|x64
{5817155C-AB74-445D-9A61-47CDE42C2BAD}.Debug|x64.Build.0 = Debug|x64
{5817155C-AB74-445D-9A61-47CDE42C2BAD}.Debug|x86.ActiveCfg = Debug|Win32
{5817155C-AB74-445D-9A61-47CDE42C2BAD}.Debug|x86.Build.0 = Debug|Win32
{5817155C-AB74-445D-9A61-47CDE42C2BAD}.Release|x64.ActiveCfg = Release|x64
{5817155C-AB74-445D-9A61-47CDE42C2BAD}.Release|x64.Build.0 = Release|x64
{5817155C-AB74-445D-9A61-47CDE42C2BAD}.Release|x86.ActiveCfg = Release|Win32
{5817155C-AB74-445D-9A61-47CDE42C2BAD}.Release|x86.Build.0 = Release|Win32
{9E765542-8C51-43B4-B0C0-C166D21CB222}.Debug|x64.ActiveCfg = Debug|x64
{9E765542-8C51-43B4-B0C0-C166D21CB222}.Debug|x64.Build.0 = Debug|x64
{9E765542-8C51-43B4-B0C0-C166D21CB222}.Debug|x86.ActiveCfg = Debug|Win32
{9E765542-8C51-43B4-B0C0-C166D21CB222}.Debug|x86.Build.0 = Debug|Win32
{9E765542-8C51-43B4-B0C0-C166D21CB222}.Release|x64.ActiveCfg = Release|x64
{9E765542-8C51-43B4-B0C0-C166D21CB222}.Release|x64.Build.0 = Release|x64
{9E765542-8C51-43B4-B0C0-C166D21CB222}.Release|x86.ActiveCfg = Release|Win32
{9E765542-8C51-43B4-B0C0-C166D21CB222}.Release|x86.Build.0 = Release|Win32
{9B2EFA45-D6F5-436B-8DFA-A675D1FECD5D}.Debug|x64.ActiveCfg = Debug|x64
{9B2EFA45-D6F5-436B-8DFA-A675D1FECD5D}.Debug|x64.Build.0 = Debug|x64
{9B2EFA45-D6F5-436B-8DFA-A675D1FECD5D}.Debug|x86.ActiveCfg = Debug|Win32
{9B2EFA45-D6F5-436B-8DFA-A675D1FECD5D}.Debug|x86.Build.0 = Debug|Win32
{9B2EFA45-D6F5-436B-8DFA-A675D1FECD5D}.Release|x64.ActiveCfg = Release|x64
{9B2EFA45-D6F5-436B-8DFA-A675D1FECD5D}.Release|x64.Build.0 = Release|x64
{9B2EFA45-D6F5-436B-8DFA-A675D1FECD5D}.Release|x86.ActiveCfg = Release|Win32
{9B2EFA45-D6F5-436B-8DFA-A675D1FECD5D}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1B93F7EF-80DC-4175-9456-C28068EF4D6D}
EndGlobalSection
EndGlobal