From 3a1746e71cfb7c5c16a0e5cf8125b75d5febb443 Mon Sep 17 00:00:00 2001 From: Corentin Wallez Date: Wed, 15 Jan 2020 13:14:12 +0000 Subject: [PATCH] Introduce wgpu::Surface and implement it for HWND, X11 and Metal This is another step to implement webgpu.h swapchains, Surface is essentially a union type of all the types of windows that can be used to create swapchains. Changes to allow implementing wgpu::Surface and test its creation are: - Add GLFWUtils.cpp/.h/_metal.mm that contains helpers used to use WebGPU with GLFW. This deprecates BackendBinding.h that will be removed when the NXT swapchain is removed. - Add a `dawn_use_x11` GN variable to factor all the places in BUILD.gn where we checked whether we should use X11. - Add a `supports_glfw_for_windowing` GN variable in the main BUILD.gn file to control which configuration tests and samples using GLFW can be built. - Add a ObjCUtils.h to contain some ObjC functionality that we'd need in files that otherwise would be C++ (so that they can be compiled on all platforms). Bug: dawn:269 Change-Id: I25548142a1d1d1f05b0f4d71aa3bdc4698d19622 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/15081 Commit-Queue: Corentin Wallez Reviewed-by: Kai Ninomiya --- BUILD.gn | 72 ++++-- dawn.json | 49 +++- dawn_wire.json | 5 +- examples/SampleUtils.cpp | 1 + generator/templates/dawn_native/ProcTable.cpp | 1 + scripts/dawn_features.gni | 3 + src/common/BUILD.gn | 2 +- src/common/windows_with_undefs.h | 1 + src/common/xlib_with_undefs.h | 2 + src/dawn_native/Instance.cpp | 9 + src/dawn_native/Instance.h | 5 + src/dawn_native/Surface.cpp | 172 +++++++++++++ src/dawn_native/Surface.h | 71 ++++++ src/dawn_native/Surface_metal.mm | 30 +++ src/tests/end2end/WindowSurfaceTests.cpp | 236 ++++++++++++++++++ src/utils/BackendBinding.cpp | 11 - src/utils/BackendBinding.h | 1 - src/utils/GLFWUtils.cpp | 83 ++++++ src/utils/GLFWUtils.h | 42 ++++ src/utils/GLFWUtils_metal.mm | 54 ++++ src/utils/ObjCUtils.h | 29 +++ src/utils/ObjCUtils.mm | 25 ++ 22 files changed, 868 insertions(+), 36 deletions(-) create mode 100644 src/dawn_native/Surface.cpp create mode 100644 src/dawn_native/Surface.h create mode 100644 src/dawn_native/Surface_metal.mm create mode 100644 src/tests/end2end/WindowSurfaceTests.cpp create mode 100644 src/utils/GLFWUtils.cpp create mode 100644 src/utils/GLFWUtils.h create mode 100644 src/utils/GLFWUtils_metal.mm create mode 100644 src/utils/ObjCUtils.h create mode 100644 src/utils/ObjCUtils.mm diff --git a/BUILD.gn b/BUILD.gn index c013899207..866b2d472e 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -263,6 +263,8 @@ source_set("libdawn_native_sources") { "src/dawn_native/ShaderModule.h", "src/dawn_native/StagingBuffer.cpp", "src/dawn_native/StagingBuffer.h", + "src/dawn_native/Surface.cpp", + "src/dawn_native/Surface.h", "src/dawn_native/SwapChain.cpp", "src/dawn_native/SwapChain.h", "src/dawn_native/Texture.cpp", @@ -346,8 +348,10 @@ source_set("libdawn_native_sources") { "Cocoa.framework", "IOKit.framework", "IOSurface.framework", + "QuartzCore.framework", ] sources += [ + "src/dawn_native/Surface_metal.mm", "src/dawn_native/metal/BackendMTL.h", "src/dawn_native/metal/BackendMTL.mm", "src/dawn_native/metal/BufferMTL.h", @@ -541,6 +545,10 @@ source_set("libdawn_native_sources") { [ "DAWN_SWIFTSHADER_VK_ICD_JSON=\"${swiftshader_icd_file_name}\"" ] } } + + if (dawn_use_x11) { + libs += [ "X11" ] + } } # The static and shared libraries for libdawn_native. Most of the files are @@ -662,10 +670,12 @@ dawn_component("libdawn_wire") { # GLFW wrapping target ############################################################################### +supports_glfw_for_windowing = is_win || (is_linux && !is_chromeos) || is_mac + # GLFW does not support ChromeOS, Android or Fuchsia, so provide a small mock # library that can be linked into the Dawn tests on these platforms. Otherwise, # use the real library from third_party/. -if (is_win || (is_linux && !is_chromeos) || is_mac) { +if (supports_glfw_for_windowing) { group("dawn_glfw") { public_deps = [ "third_party:glfw", @@ -724,25 +734,43 @@ static_library("dawn_utils") { "src/utils/WGPUHelpers.cpp", "src/utils/WGPUHelpers.h", ] - - if (is_win) { - sources += [ "src/utils/WindowsTimer.cpp" ] - } else if (is_mac) { - sources += [ "src/utils/OSXTimer.cpp" ] - } else { - sources += [ "src/utils/PosixTimer.cpp" ] - } - - public_deps = [ - "${dawn_root}/src/dawn:dawncpp_headers", - ] - deps = [ ":libdawn_native", ":libdawn_wire", "${dawn_root}/src/common", "${dawn_shaderc_dir}:libshaderc", ] + libs = [] + + if (is_win) { + sources += [ "src/utils/WindowsTimer.cpp" ] + } else if (is_mac) { + sources += [ + "src/utils/OSXTimer.cpp", + "src/utils/ObjCUtils.h", + "src/utils/ObjCUtils.mm", + ] + libs += [ "QuartzCore.framework" ] + } else { + sources += [ "src/utils/PosixTimer.cpp" ] + } + + if (supports_glfw_for_windowing) { + sources += [ + "src/utils/GLFWUtils.cpp", + "src/utils/GLFWUtils.h", + ] + deps += [ ":dawn_glfw" ] + + if (dawn_enable_metal) { + sources += [ "src/utils/GLFWUtils_metal.mm" ] + libs += [ "Metal.framework" ] + } + } + + public_deps = [ + "${dawn_root}/src/dawn:dawncpp_headers", + ] } ############################################################################### @@ -915,12 +943,6 @@ source_set("dawn_end2end_tests_sources") { libs = [] - if (dawn_enable_metal) { - sources += [ "src/tests/end2end/IOSurfaceWrappingTests.cpp" ] - - libs += [ "IOSurface.framework" ] - } - if (dawn_enable_d3d12) { sources += [ "src/tests/end2end/D3D12ResourceWrappingTests.cpp" ] libs += [ @@ -929,7 +951,17 @@ source_set("dawn_end2end_tests_sources") { ] } + if (dawn_enable_metal) { + sources += [ "src/tests/end2end/IOSurfaceWrappingTests.cpp" ] + libs += [ "IOSurface.framework" ] + } + if (dawn_enable_opengl) { + assert(supports_glfw_for_windowing) + } + + if (supports_glfw_for_windowing) { + sources += [ "src/tests/end2end/WindowSurfaceTests.cpp" ] deps += [ ":dawn_glfw" ] } } diff --git a/dawn.json b/dawn.json index 8f1b0b71aa..c8a318aea5 100644 --- a/dawn.json +++ b/dawn.json @@ -771,7 +771,16 @@ ] }, "instance": { - "category": "object" + "category": "object", + "methods": [ + { + "name": "create surface", + "returns": "surface", + "args": [ + {"name": "descriptor", "type": "surface descriptor", "annotation": "const*"} + ] + } + ] }, "instance descriptor": { "category": "structure", @@ -1274,6 +1283,39 @@ {"name": "pass op", "type": "stencil operation", "default": "keep"} ] }, + "surface": { + "category": "object" + }, + "surface descriptor": { + "category": "structure", + "extensible": true, + "members": [ + {"name": "label", "type": "char", "annotation": "const*", "length": "strlen", "optional": true} + ] + }, + "surface descriptor from metal layer": { + "category": "structure", + "chained": true, + "members": [ + {"name": "layer", "type": "void", "annotation": "*"} + ] + }, + "surface descriptor from windows HWND": { + "category": "structure", + "chained": true, + "members": [ + {"name": "hinstance", "type": "void", "annotation": "*"}, + {"name": "hwnd", "type": "void", "annotation": "*"} + ] + }, + "surface descriptor from xlib": { + "category": "structure", + "chained": true, + "members": [ + {"name": "display", "type": "void", "annotation": "*"}, + {"name": "window", "type": "uint32_t"} + ] + }, "swap chain": { "category": "object", "methods": [ @@ -1301,7 +1343,10 @@ "s type": { "category": "enum", "values": [ - {"value": 0, "name": "invalid"} + {"value": 0, "name": "invalid"}, + {"value": 1, "name": "surface descriptor from metal layer"}, + {"value": 2, "name": "surface descriptor from windows HWND"}, + {"value": 3, "name": "surface descriptor from xlib"} ] }, "texture": { diff --git a/dawn_wire.json b/dawn_wire.json index a8cffcb3a0..6493349fd8 100644 --- a/dawn_wire.json +++ b/dawn_wire.json @@ -89,7 +89,10 @@ }, "special items": { "client_side_structures": [ - "CreateBufferMappedResult" + "CreateBufferMappedResult", + "SurfaceDescriptorFromMetalLayer", + "SurfaceDescriptorFromWindowsHWND", + "SurfaceDescriptorFromXlib" ], "client_side_commands": [ "BufferMapReadAsync", diff --git a/examples/SampleUtils.cpp b/examples/SampleUtils.cpp index b3d87a1e57..031f5d5662 100644 --- a/examples/SampleUtils.cpp +++ b/examples/SampleUtils.cpp @@ -18,6 +18,7 @@ #include "common/Log.h" #include "common/Platform.h" #include "utils/BackendBinding.h" +#include "utils/GLFWUtils.h" #include "utils/TerribleCommandBuffer.h" #include diff --git a/generator/templates/dawn_native/ProcTable.cpp b/generator/templates/dawn_native/ProcTable.cpp index 1713ac2c49..88b780c907 100644 --- a/generator/templates/dawn_native/ProcTable.cpp +++ b/generator/templates/dawn_native/ProcTable.cpp @@ -33,6 +33,7 @@ namespace dawn_native { using FenceBase = Fence; using RenderPassEncoderBase = RenderPassEncoder; using RenderBundleEncoderBase = RenderBundleEncoder; + using SurfaceBase = Surface; namespace { diff --git a/scripts/dawn_features.gni b/scripts/dawn_features.gni index c82d22827c..2ef8e15190 100644 --- a/scripts/dawn_features.gni +++ b/scripts/dawn_features.gni @@ -54,6 +54,9 @@ declare_args() { # Enables error injection for faking failures to native API calls dawn_enable_error_injection = is_debug || (build_with_chromium && use_fuzzing_engine) + + # Whether Dawn should enable X11 support. + dawn_use_x11 = is_linux && !is_chromeos } # GN does not allow reading a variable defined in the same declare_args(). diff --git a/src/common/BUILD.gn b/src/common/BUILD.gn index 6306ed92fc..14bcf0a29d 100644 --- a/src/common/BUILD.gn +++ b/src/common/BUILD.gn @@ -75,7 +75,7 @@ config("dawn_internal") { defines += [ "DAWN_ENABLE_BACKEND_VULKAN" ] } - if (is_linux && !is_chromeos) { + if (dawn_use_x11) { defines += [ "DAWN_USE_X11" ] } diff --git a/src/common/windows_with_undefs.h b/src/common/windows_with_undefs.h index e19552f30a..381116a024 100644 --- a/src/common/windows_with_undefs.h +++ b/src/common/windows_with_undefs.h @@ -26,6 +26,7 @@ #include // Macros defined for ANSI / Unicode support +#undef CreateWindow #undef GetMessage // Macros defined to produce compiler intrinsics diff --git a/src/common/xlib_with_undefs.h b/src/common/xlib_with_undefs.h index 794ce0fb49..f82a19aa2d 100644 --- a/src/common/xlib_with_undefs.h +++ b/src/common/xlib_with_undefs.h @@ -30,4 +30,6 @@ #undef None #undef Always +using XErrorHandler = int (*)(Display*, XErrorEvent*); + #endif // COMMON_XLIB_WITH_UNDEFS_H_ diff --git a/src/dawn_native/Instance.cpp b/src/dawn_native/Instance.cpp index 735c61717f..982c979728 100644 --- a/src/dawn_native/Instance.cpp +++ b/src/dawn_native/Instance.cpp @@ -17,6 +17,7 @@ #include "common/Assert.h" #include "common/Log.h" #include "dawn_native/ErrorData.h" +#include "dawn_native/Surface.h" namespace dawn_native { @@ -211,4 +212,12 @@ namespace dawn_native { return mPlatform; } + Surface* InstanceBase::CreateSurface(const SurfaceDescriptor* descriptor) { + if (ConsumedError(ValidateSurfaceDescriptor(this, descriptor))) { + return nullptr; + } + + return new Surface(this, descriptor); + } + } // namespace dawn_native diff --git a/src/dawn_native/Instance.h b/src/dawn_native/Instance.h index 5095664fdc..e07d04b6b4 100644 --- a/src/dawn_native/Instance.h +++ b/src/dawn_native/Instance.h @@ -29,6 +29,8 @@ namespace dawn_native { + class Surface; + // This is called InstanceBase for consistency across the frontend, even if the backends don't // specialize this class. class InstanceBase final : public RefCounted { @@ -64,6 +66,9 @@ namespace dawn_native { void SetPlatform(dawn_platform::Platform* platform); dawn_platform::Platform* GetPlatform() const; + // Dawn API + Surface* CreateSurface(const SurfaceDescriptor* descriptor); + private: InstanceBase() = default; ~InstanceBase() = default; diff --git a/src/dawn_native/Surface.cpp b/src/dawn_native/Surface.cpp new file mode 100644 index 0000000000..0d0214449e --- /dev/null +++ b/src/dawn_native/Surface.cpp @@ -0,0 +1,172 @@ +// Copyright 2020 the Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dawn_native/Surface.h" + +#include "common/Platform.h" +#include "dawn_native/Instance.h" + +#if defined(DAWN_PLATFORM_WINDOWS) +# include "common/windows_with_undefs.h" +#endif // DAWN_PLATFORM_WINDOWS + +#if defined(DAWN_USE_X11) +# include "common/xlib_with_undefs.h" +#endif // defined(DAWN_USE_X11) + +namespace dawn_native { + +#if defined(DAWN_ENABLE_BACKEND_METAL) + bool InheritsFromCAMetalLayer(void* obj); +#endif // defined(DAWN_ENABLE_BACKEND_METAL) + + MaybeError ValidateSurfaceDescriptor(const InstanceBase* instance, + const SurfaceDescriptor* descriptor) { + // TODO(cwallez@chromium.org): Have some type of helper to iterate over all the chained + // structures. + if (descriptor->nextInChain == nullptr) { + return DAWN_VALIDATION_ERROR("Surface cannot be created with just the base descriptor"); + } + + const ChainedStruct* chainedDescriptor = descriptor->nextInChain; + if (chainedDescriptor->nextInChain != nullptr) { + return DAWN_VALIDATION_ERROR("Cannot specify two windows for a single surface"); + } + + switch (chainedDescriptor->sType) { +#if defined(DAWN_ENABLE_BACKEND_METAL) + case wgpu::SType::SurfaceDescriptorFromMetalLayer: { + const SurfaceDescriptorFromMetalLayer* metalDesc = + static_cast(chainedDescriptor); + + // Check that the layer is a CAMetalLayer (or a derived class). + if (!InheritsFromCAMetalLayer(metalDesc->layer)) { + return DAWN_VALIDATION_ERROR("layer must be a CAMetalLayer"); + } + } break; +#endif // defined(DAWN_ENABLE_BACKEND_METAL) + +#if defined(DAWN_PLATFORM_WINDOWS) + case wgpu::SType::SurfaceDescriptorFromWindowsHWND: { + const SurfaceDescriptorFromWindowsHWND* hwndDesc = + static_cast(chainedDescriptor); + + // It is not possible to validate an HINSTANCE. + + // Validate the hwnd using the windows.h IsWindow function. + if (IsWindow(static_cast(hwndDesc->hwnd)) == 0) { + return DAWN_VALIDATION_ERROR("Invalid HWND"); + } + } break; +#endif // defined(DAWN_PLATFORM_WINDOWS) + +#if defined(DAWN_USE_X11) + case wgpu::SType::SurfaceDescriptorFromXlib: { + const SurfaceDescriptorFromXlib* xDesc = + static_cast(chainedDescriptor); + + // It is not possible to validate an X Display. + + // Check the validity of the window by calling a getter function on the window that + // returns a status code. If the window is bad the call return a status of zero. We + // need to set a temporary X11 error handler while doing this because the default + // X11 error handler exits the program on any error. + XErrorHandler oldErrorHandler = + XSetErrorHandler([](Display*, XErrorEvent*) { return 0; }); + XWindowAttributes attributes; + int status = XGetWindowAttributes(reinterpret_cast(xDesc->display), + xDesc->window, &attributes); + XSetErrorHandler(oldErrorHandler); + + if (status == 0) { + return DAWN_VALIDATION_ERROR("Invalid X Window"); + } + } break; +#endif // defined(DAWN_USE_X11) + + default: + return DAWN_VALIDATION_ERROR("Unsupported sType"); + } + + return {}; + } + + Surface::Surface(InstanceBase* instance, const SurfaceDescriptor* descriptor) + : mInstance(instance) { + ASSERT(descriptor->nextInChain != nullptr); + const ChainedStruct* chainedDescriptor = descriptor->nextInChain; + + switch (chainedDescriptor->sType) { + case wgpu::SType::SurfaceDescriptorFromMetalLayer: { + const SurfaceDescriptorFromMetalLayer* metalDesc = + static_cast(chainedDescriptor); + mType = Type::MetalLayer; + mMetalLayer = metalDesc->layer; + } break; + + case wgpu::SType::SurfaceDescriptorFromWindowsHWND: { + const SurfaceDescriptorFromWindowsHWND* hwndDesc = + static_cast(chainedDescriptor); + mType = Type::WindowsHWND; + mHInstance = hwndDesc->hinstance; + mHWND = hwndDesc->hwnd; + } break; + + case wgpu::SType::SurfaceDescriptorFromXlib: { + const SurfaceDescriptorFromXlib* xDesc = + static_cast(chainedDescriptor); + mType = Type::Xlib; + mXDisplay = xDesc->display; + mXWindow = xDesc->window; + } break; + + default: + UNREACHABLE(); + } + } + + Surface::~Surface() = default; + + InstanceBase* Surface::GetInstance() { + return mInstance.Get(); + } + + Surface::Type Surface::GetType() const { + return mType; + } + + void* Surface::GetMetalLayer() const { + ASSERT(mType == Type::MetalLayer); + return mMetalLayer; + } + + void* Surface::GetHInstance() const { + ASSERT(mType == Type::WindowsHWND); + return mHInstance; + } + void* Surface::GetHWND() const { + ASSERT(mType == Type::WindowsHWND); + return mHWND; + } + + void* Surface::GetXDisplay() const { + ASSERT(mType == Type::Xlib); + return mXDisplay; + } + uint32_t Surface::GetXWindow() const { + ASSERT(mType == Type::Xlib); + return mXWindow; + } + +} // namespace dawn_native diff --git a/src/dawn_native/Surface.h b/src/dawn_native/Surface.h new file mode 100644 index 0000000000..8f9ca146ad --- /dev/null +++ b/src/dawn_native/Surface.h @@ -0,0 +1,71 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DAWNNATIVE_SURFACE_H_ +#define DAWNNATIVE_SURFACE_H_ + +#include "dawn_native/Error.h" +#include "dawn_native/Forward.h" +#include "dawn_native/RefCounted.h" + +#include "dawn_native/dawn_platform.h" + +namespace dawn_native { + + MaybeError ValidateSurfaceDescriptor(const InstanceBase* instance, + const SurfaceDescriptor* descriptor); + + // A surface is a sum types of all the kind of windows Dawn supports. The OS-specific types + // aren't used because they would cause compilation errors on other OSes (or require + // ObjectiveC). + class Surface final : public RefCounted { + public: + Surface(InstanceBase* instance, const SurfaceDescriptor* descriptor); + ~Surface(); + + // These are valid to call on all Surfaces. + enum class Type { MetalLayer, WindowsHWND, Xlib }; + Type GetType() const; + InstanceBase* GetInstance(); + + // Valid to call if the type is MetalLayer + void* GetMetalLayer() const; + + // Valid to call if the type is WindowsHWND + void* GetHInstance() const; + void* GetHWND() const; + + // Valid to call if the type is WindowsXlib + void* GetXDisplay() const; + uint32_t GetXWindow() const; + + private: + Ref mInstance; + Type mType; + + // MetalLayer + void* mMetalLayer = nullptr; + + // WindowsHwnd + void* mHInstance = nullptr; + void* mHWND = nullptr; + + // Xlib + void* mXDisplay = nullptr; + uint32_t mXWindow = 0; + }; + +} // namespace dawn_native + +#endif // DAWNNATIVE_SURFACE_H_ diff --git a/src/dawn_native/Surface_metal.mm b/src/dawn_native/Surface_metal.mm new file mode 100644 index 0000000000..9989674fe6 --- /dev/null +++ b/src/dawn_native/Surface_metal.mm @@ -0,0 +1,30 @@ +// Copyright 2020 the Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Contains a helper function for Surface.cpp that needs to be written in ObjectiveC. + +#if !defined(DAWN_ENABLE_BACKEND_METAL) +# error "Surface_metal.mm requires the Metal backend to be enabled." +#endif // !defined(DAWN_ENABLE_BACKEND_METAL) + +#import + +namespace dawn_native { + + bool InheritsFromCAMetalLayer(void* obj) { + id object = static_cast(obj); + return [object isKindOfClass:[CAMetalLayer class]]; + } + +} // namespace dawn_native diff --git a/src/tests/end2end/WindowSurfaceTests.cpp b/src/tests/end2end/WindowSurfaceTests.cpp new file mode 100644 index 0000000000..beb301b592 --- /dev/null +++ b/src/tests/end2end/WindowSurfaceTests.cpp @@ -0,0 +1,236 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "common/Log.h" +#include "common/Platform.h" +#include "dawn/dawn_proc.h" +#include "dawn_native/DawnNative.h" +#include "utils/GLFWUtils.h" + +#include +#include "GLFW/glfw3.h" + +#include + +#if defined(DAWN_PLATFORM_WINDOWS) +# include "common/windows_with_undefs.h" +#endif // defined(DAWN_PLATFORM_WINDOWS) + +#if defined(DAWN_USE_X11) +# include "common/xlib_with_undefs.h" +#endif // defined(DAWN_USE_X11) + +#if defined(DAWN_ENABLE_BACKEND_METAL) +# include "utils/ObjCUtils.h" +#endif // defined(DAWN_ENABLE_BACKEND_METAL) + +#include "GLFW/glfw3native.h" + +class WindowSurfaceInstanceTests : public testing::Test { + public: + void SetUp() override { + glfwSetErrorCallback([](int code, const char* message) { + dawn::ErrorLog() << "GLFW error " << code << " " << message; + }); + glfwInit(); + + DawnProcTable procs = dawn_native::GetProcs(); + dawnProcSetProcs(&procs); + + mInstance = wgpu::CreateInstance(); + } + + void TearDown() override { + if (mWindow != nullptr) { + glfwDestroyWindow(mWindow); + mWindow = nullptr; + } + } + + void AssertSurfaceCreation(const wgpu::SurfaceDescriptor* descriptor, bool succeeds) { + ASSERT_EQ(mInstance.CreateSurface(descriptor).Get() != nullptr, succeeds); + } + + GLFWwindow* CreateWindow() { + // The WindowSurfaceInstance tests don't create devices so we don't need to call + // SetupGLFWWindowHintsForBackend. Set GLFW_NO_API anyway to avoid GLFW bringing up a GL + // context that we won't use. + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + mWindow = glfwCreateWindow(400, 400, "WindowSurfaceInstanceTests window", nullptr, nullptr); + return mWindow; + } + + private: + wgpu::Instance mInstance; + GLFWwindow* mWindow = nullptr; +}; + +// Test that a valid chained descriptor works (and that GLFWUtils creates a valid chained +// descriptor). +TEST_F(WindowSurfaceInstanceTests, ControlCase) { + GLFWwindow* window = CreateWindow(); + std::unique_ptr chainedDescriptor = + utils::SetupWindowAndGetSurfaceDescriptorForTesting(window); + + wgpu::SurfaceDescriptor descriptor; + descriptor.nextInChain = chainedDescriptor.get(); + + AssertSurfaceCreation(&descriptor, true); +} + +// Test that just wgpu::SurfaceDescriptor isn't enough and needs a chained descriptor. +TEST_F(WindowSurfaceInstanceTests, NoChainedDescriptors) { + wgpu::SurfaceDescriptor descriptor; + descriptor.nextInChain = nullptr; // That's the default value but we set it for clarity. + + AssertSurfaceCreation(&descriptor, false); +} + +// Test that a chained descriptor with a garbage sType produces an error. +TEST_F(WindowSurfaceInstanceTests, BadChainedDescriptors) { + wgpu::ChainedStruct chainedDescriptor; + chainedDescriptor.sType = wgpu::SType::Invalid; // The default but we set it for clarity. + + wgpu::SurfaceDescriptor descriptor; + descriptor.nextInChain = &chainedDescriptor; + + AssertSurfaceCreation(&descriptor, false); +} + +// Test that it is invalid to give two valid chained descriptors +TEST_F(WindowSurfaceInstanceTests, TwoChainedDescriptors) { + GLFWwindow* window = CreateWindow(); + std::unique_ptr chainedDescriptor1 = + utils::SetupWindowAndGetSurfaceDescriptorForTesting(window); + std::unique_ptr chainedDescriptor2 = + utils::SetupWindowAndGetSurfaceDescriptorForTesting(window); + + wgpu::SurfaceDescriptor descriptor; + descriptor.nextInChain = chainedDescriptor1.get(); + chainedDescriptor1->nextInChain = chainedDescriptor2.get(); + + AssertSurfaceCreation(&descriptor, false); +} + +#if defined(DAWN_PLATFORM_WINDOWS) + +// Tests that GLFWUtils returns a descriptor of HWND type +TEST_F(WindowSurfaceInstanceTests, CorrectSTypeHWND) { + GLFWwindow* window = CreateWindow(); + std::unique_ptr chainedDescriptor = + utils::SetupWindowAndGetSurfaceDescriptorForTesting(window); + ASSERT_EQ(chainedDescriptor->sType, wgpu::SType::SurfaceDescriptorFromWindowsHWND); +} + +// Test with setting an invalid hwnd +TEST_F(WindowSurfaceInstanceTests, InvalidHWND) { + wgpu::SurfaceDescriptorFromWindowsHWND chainedDescriptor; + chainedDescriptor.hinstance = GetModuleHandle(nullptr); + chainedDescriptor.hwnd = 0; // This always is an invalid HWND value. + + wgpu::SurfaceDescriptor descriptor; + descriptor.nextInChain = &chainedDescriptor; + AssertSurfaceCreation(&descriptor, false); +} + +#else // defined(DAWN_PLATFORM_WINDOWS) + +// Test using HWND when it is not supported +TEST_F(WindowSurfaceInstanceTests, HWNDSurfacesAreInvalid) { + wgpu::SurfaceDescriptorFromWindowsHWND chainedDescriptor; + chainedDescriptor.hinstance = nullptr; + chainedDescriptor.hwnd = 0; + + wgpu::SurfaceDescriptor descriptor; + descriptor.nextInChain = &chainedDescriptor; + AssertSurfaceCreation(&descriptor, false); +} + +#endif // defined(DAWN_PLATFORM_WINDOWS) + +#if defined(DAWN_USE_X11) + +// Tests that GLFWUtils returns a descriptor of Xlib type +TEST_F(WindowSurfaceInstanceTests, CorrectSTypeXlib) { + GLFWwindow* window = CreateWindow(); + std::unique_ptr chainedDescriptor = + utils::SetupWindowAndGetSurfaceDescriptorForTesting(window); + ASSERT_EQ(chainedDescriptor->sType, wgpu::SType::SurfaceDescriptorFromXlib); +} + +// Test with setting an invalid window +TEST_F(WindowSurfaceInstanceTests, InvalidXWindow) { + wgpu::SurfaceDescriptorFromXlib chainedDescriptor; + chainedDescriptor.display = XOpenDisplay(nullptr); + // From the "X Window System Protocol" "X Version 11, Release 6.8" page 2 at + // https://www.x.org/releases/X11R7.5/doc/x11proto/proto.pdf + // WINDOW 32-bit value (top three bits guaranteed to be zero. + // So UINT32_MAX should be an invalid window. + chainedDescriptor.window = 0xFFFFFFFF; + + wgpu::SurfaceDescriptor descriptor; + descriptor.nextInChain = &chainedDescriptor; + AssertSurfaceCreation(&descriptor, false); +} + +#else // defined(DAWN_USE_X11) + +// Test using Xlib when it is not supported +TEST_F(WindowSurfaceInstanceTests, XlibSurfacesAreInvalid) { + wgpu::SurfaceDescriptorFromXlib chainedDescriptor; + chainedDescriptor.display = nullptr; + chainedDescriptor.window = 0; + + wgpu::SurfaceDescriptor descriptor; + descriptor.nextInChain = &chainedDescriptor; + AssertSurfaceCreation(&descriptor, false); +} + +#endif // defined(DAWN_USE_X11) + +#if defined(DAWN_ENABLE_BACKEND_METAL) + +// Tests that GLFWUtils returns a descriptor of Metal type +TEST_F(WindowSurfaceInstanceTests, CorrectSTypeMetal) { + GLFWwindow* window = CreateWindow(); + std::unique_ptr chainedDescriptor = + utils::SetupWindowAndGetSurfaceDescriptorForTesting(window); + ASSERT_EQ(chainedDescriptor->sType, wgpu::SType::SurfaceDescriptorFromMetalLayer); +} + +// Test with setting an invalid layer +TEST_F(WindowSurfaceInstanceTests, InvalidMetalLayer) { + wgpu::SurfaceDescriptorFromMetalLayer chainedDescriptor; + // The CALayer is autoreleased. Releasing it causes a test failure when the Chromium GTest + // autoreleasepool is emptied. + chainedDescriptor.layer = utils::CreateDummyCALayer(); + + wgpu::SurfaceDescriptor descriptor; + descriptor.nextInChain = &chainedDescriptor; + AssertSurfaceCreation(&descriptor, false); +} + +#else // defined(DAWN_ENABLE_BACKEND_METAL) + +// Test using Metal when it is not supported +TEST_F(WindowSurfaceInstanceTests, MetalSurfacesAreInvalid) { + wgpu::SurfaceDescriptorFromMetalLayer chainedDescriptor; + chainedDescriptor.layer = nullptr; + + wgpu::SurfaceDescriptor descriptor; + descriptor.nextInChain = &chainedDescriptor; + AssertSurfaceCreation(&descriptor, false); +} + +#endif // defined(DAWN_ENABLE_BACKEND_METAL) diff --git a/src/utils/BackendBinding.cpp b/src/utils/BackendBinding.cpp index 0e4fff3e2c..4fe17b0f83 100644 --- a/src/utils/BackendBinding.cpp +++ b/src/utils/BackendBinding.cpp @@ -44,17 +44,6 @@ namespace utils { : mWindow(window), mDevice(device) { } - void SetupGLFWWindowHintsForBackend(wgpu::BackendType type) { - if (type == wgpu::BackendType::OpenGL) { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 4); - glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - } else { - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - } - } - void DiscoverAdapter(dawn_native::Instance* instance, GLFWwindow* window, wgpu::BackendType type) { diff --git a/src/utils/BackendBinding.h b/src/utils/BackendBinding.h index 26d749a7f3..ca1c91ffb9 100644 --- a/src/utils/BackendBinding.h +++ b/src/utils/BackendBinding.h @@ -36,7 +36,6 @@ namespace utils { WGPUDevice mDevice = nullptr; }; - void SetupGLFWWindowHintsForBackend(wgpu::BackendType type); void DiscoverAdapter(dawn_native::Instance* instance, GLFWwindow* window, wgpu::BackendType type); diff --git a/src/utils/GLFWUtils.cpp b/src/utils/GLFWUtils.cpp new file mode 100644 index 0000000000..fe9195ed6a --- /dev/null +++ b/src/utils/GLFWUtils.cpp @@ -0,0 +1,83 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "utils/GLFWUtils.h" + +#include "GLFW/glfw3.h" +#include "common/Platform.h" + +#include + +#if defined(DAWN_PLATFORM_WINDOWS) +# define GLFW_EXPOSE_NATIVE_WIN32 +#elif defined(DAWN_USE_X11) +# define GLFW_EXPOSE_NATIVE_X11 +#endif +#include "GLFW/glfw3native.h" + +namespace utils { + + void SetupGLFWWindowHintsForBackend(wgpu::BackendType type) { + if (type == wgpu::BackendType::OpenGL) { + // Ask for OpenGL 4.4 which is what the GL backend requires for compute shaders and + // texture views. + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 4); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + } else { + // Without this GLFW will initialize a GL context on the window, which prevents using + // the window with other APIs (by crashing in weird ways). + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + } + } + + wgpu::Surface CreateSurfaceForWindow(wgpu::Instance instance, GLFWwindow* window) { + std::unique_ptr chainedDescriptor = + SetupWindowAndGetSurfaceDescriptorForTesting(window); + + wgpu::SurfaceDescriptor descriptor; + descriptor.nextInChain = chainedDescriptor.get(); + wgpu::Surface surface = instance.CreateSurface(&descriptor); + + return surface; + } + +#if defined(DAWN_PLATFORM_WINDOWS) + std::unique_ptr SetupWindowAndGetSurfaceDescriptorForTesting( + GLFWwindow* window) { + std::unique_ptr desc = + std::make_unique(); + desc->hwnd = glfwGetWin32Window(window); + desc->hinstance = GetModuleHandle(nullptr); + return desc; + } +#elif defined(DAWN_USE_X11) + std::unique_ptr SetupWindowAndGetSurfaceDescriptorForTesting( + GLFWwindow* window) { + std::unique_ptr desc = + std::make_unique(); + desc->display = glfwGetX11Display(); + desc->window = glfwGetX11Window(window); + return desc; + } +#elif defined(DAWN_ENABLE_BACKEND_METAL) + // SetupWindowAndGetSurfaceDescriptorForTesting defined in GLFWUtils_metal.mm +#else + std::unique_ptr SetupWindowAndGetSurfaceDescriptorForTesting(GLFWwindow*) { + return nullptr; + } +#endif + +} // namespace utils diff --git a/src/utils/GLFWUtils.h b/src/utils/GLFWUtils.h new file mode 100644 index 0000000000..f2299cba9d --- /dev/null +++ b/src/utils/GLFWUtils.h @@ -0,0 +1,42 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef UTILS_GLFWUTILS_H_ +#define UTILS_GLFWUTILS_H_ + +#include "dawn/webgpu_cpp.h" + +#include + +struct GLFWwindow; + +namespace utils { + + // Adds all the necessary glfwWindowHint calls for the next GLFWwindow created to be used with + // the specified backend. + void SetupGLFWWindowHintsForBackend(wgpu::BackendType type); + + // Does the necessary setup on the GLFWwindow to allow creating a wgpu::Surface with it and + // calls `instance.CreateSurface` with the correct descriptor for this window. + // Returns a null wgpu::Surface on failure. + wgpu::Surface CreateSurfaceForWindow(wgpu::Instance instance, GLFWwindow* window); + + // Use for testing only. Does everything that CreateSurfaceForWindow does except the call to + // CreateSurface so the descriptor can be modified for testing. + std::unique_ptr SetupWindowAndGetSurfaceDescriptorForTesting( + GLFWwindow* window); + +} // namespace utils + +#endif // UTILS_GLFWUTILS_H_ diff --git a/src/utils/GLFWUtils_metal.mm b/src/utils/GLFWUtils_metal.mm new file mode 100644 index 0000000000..ff0942885c --- /dev/null +++ b/src/utils/GLFWUtils_metal.mm @@ -0,0 +1,54 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#if !defined(DAWN_ENABLE_BACKEND_METAL) +# error "GLFWUtils_metal.mm requires the Metal backend to be enabled." +#endif // !defined(DAWN_ENABLE_BACKEND_METAL) + +#include "utils/GLFWUtils.h" + +#import +#include "GLFW/glfw3.h" + +#include + +#define GLFW_EXPOSE_NATIVE_COCOA +#include "GLFW/glfw3native.h" + +namespace utils { + + std::unique_ptr SetupWindowAndGetSurfaceDescriptorForTesting( + GLFWwindow* window) { + if (@available(macOS 10.11, *)) { + NSWindow* nsWindow = glfwGetCocoaWindow(window); + NSView* view = [nsWindow contentView]; + + // Create a CAMetalLayer that covers the whole window that will be passed to + // CreateSurface. + [view setWantsLayer:YES]; + [view setLayer:[CAMetalLayer layer]]; + + // Use retina if the window was created with retina support. + [[view layer] setContentsScale:[nsWindow backingScaleFactor]]; + + std::unique_ptr desc = + std::make_unique(); + desc->layer = [view layer]; + return desc; + } + + return nullptr; + } + +} // namespace utils diff --git a/src/utils/ObjCUtils.h b/src/utils/ObjCUtils.h new file mode 100644 index 0000000000..17b3956a16 --- /dev/null +++ b/src/utils/ObjCUtils.h @@ -0,0 +1,29 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef UTILS_OBJCUTILS_H_ +#define UTILS_OBJCUTILS_H_ + +// Contains helper function to manipulate ObjC objects. This helps having C++ files do a little bit +// of ObjectiveC calls, when they cannot be converted to ObjectiveC++ because they are used on +// multiple platforms. + +namespace utils { + + // The returned CALayer is autoreleased. + void* CreateDummyCALayer(); + +} // namespace utils + +#endif // UTILS_OBJCUTILS_H_ diff --git a/src/utils/ObjCUtils.mm b/src/utils/ObjCUtils.mm new file mode 100644 index 0000000000..5eba147cb6 --- /dev/null +++ b/src/utils/ObjCUtils.mm @@ -0,0 +1,25 @@ +// Copyright 2020 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "utils/ObjCUtils.h" + +#include + +namespace utils { + + void* CreateDummyCALayer() { + return [CALayer layer]; + } + +} // namespace utils