From c083d65a0c19a53703a00c3e9d8846935a0cfcd6 Mon Sep 17 00:00:00 2001 From: Loko Kung Date: Tue, 12 Apr 2022 23:50:56 +0000 Subject: [PATCH] Adds device-side cache key generation. - Note that the device-side cache key will be prepended to object cache keys to prevent incompatible adapter/device cache clashes. - Adds a new template file to auto=generate these cache serializers based on arguments. Bug: dawn:549 Change-Id: I24b9d11eb38c579acfcc173a5dced9e1b649cf2c Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/86081 Reviewed-by: Austin Eng Commit-Queue: Loko Kung --- generator/dawn_json_generator.py | 4 ++ generator/templates/dawn/native/CacheKey.cpp | 68 +++++++++++++++++++ src/dawn/native/BUILD.gn | 1 + src/dawn/native/CacheKey.h | 36 ++++++++++ src/dawn/native/Device.cpp | 18 +++-- src/dawn/native/Device.h | 5 +- .../unittests/native/DeviceCreationTests.cpp | 54 +++++++++------ 7 files changed, 160 insertions(+), 26 deletions(-) create mode 100644 generator/templates/dawn/native/CacheKey.cpp diff --git a/generator/dawn_json_generator.py b/generator/dawn_json_generator.py index 29c1be93db..d02213de35 100644 --- a/generator/dawn_json_generator.py +++ b/generator/dawn_json_generator.py @@ -955,6 +955,10 @@ class MultiGeneratorFromDawnJSON(Generator): FileRender('dawn/native/ObjectType.cpp', 'src/' + native_dir + '/ObjectType_autogen.cpp', frontend_params)) + renders.append( + FileRender('dawn/native/CacheKey.cpp', + 'src/' + native_dir + '/CacheKey_autogen.cpp', + frontend_params)) if 'wire' in targets: params_dawn_wire = parse_json(loaded_json, diff --git a/generator/templates/dawn/native/CacheKey.cpp b/generator/templates/dawn/native/CacheKey.cpp new file mode 100644 index 0000000000..cf31449967 --- /dev/null +++ b/generator/templates/dawn/native/CacheKey.cpp @@ -0,0 +1,68 @@ +//* Copyright 2022 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. + +{% set impl_dir = metadata.impl_dir + "/" if metadata.impl_dir else "" %} +{% set namespace_name = Name(metadata.native_namespace) %} +{% set native_namespace = namespace_name.namespace_case() %} +{% set native_dir = impl_dir + namespace_name.Dirs() %} +{% set prefix = metadata.proc_table_prefix.lower() %} +#include "{{native_dir}}/CacheKey.h" +#include "{{native_dir}}/{{prefix}}_platform.h" + +#include + +namespace {{native_namespace}} { + +// +// Cache key serializers for wgpu structures used in caching. +// +{% macro render_serializer(member) %} + {%- set name = member.name.camelCase() -%} + {% if member.length == None %} + key->Record(t.{{name}}); + {% elif member.length == "strlen" %} + key->RecordIterable(t.{{name}}, strlen(t.{{name}})); + {% else %} + key->RecordIterable(t.{{name}}, t.{{member.length.name.camelCase()}}); + {% endif %} +{% endmacro %} + +{# Helper macro to render serializers. Should be used in a call block to provide additional custom + handling when necessary. The optional `omit` field can be used to omit fields that are either + handled in the custom code, or unnecessary in the serialized output. + Example: + {% call render_cache_key_serializer("struct name", omits=["omit field"]) %} + // Custom C++ code to handle special types/members that are hard to generate code for + {% endcall %} +#} +{% macro render_cache_key_serializer(json_type, omits=[]) %} + {%- set cpp_type = types[json_type].name.CamelCase() -%} + template <> + void CacheKeySerializer<{{cpp_type}}>::Serialize(CacheKey* key, const {{cpp_type}}& t) { + {{ caller() }} + {% for member in types[json_type].members %} + {%- if not member.name.get() in omits %} + {{render_serializer(member)}} + {%- endif %} + {% endfor %} + } +{% endmacro %} + +{% call render_cache_key_serializer("adapter properties") %} +{% endcall %} + +{% call render_cache_key_serializer("dawn cache device descriptor") %} +{% endcall %} + +} // namespace {{native_namespace}} diff --git a/src/dawn/native/BUILD.gn b/src/dawn/native/BUILD.gn index e6d90dca33..b15dd8efd1 100644 --- a/src/dawn/native/BUILD.gn +++ b/src/dawn/native/BUILD.gn @@ -119,6 +119,7 @@ dawn_json_generator("utils_gen") { "src/dawn/native/webgpu_absl_format_autogen.cpp", "src/dawn/native/ObjectType_autogen.h", "src/dawn/native/ObjectType_autogen.cpp", + "src/dawn/native/CacheKey_autogen.cpp", ] } diff --git a/src/dawn/native/CacheKey.h b/src/dawn/native/CacheKey.h index 4fff61b95d..4772104195 100644 --- a/src/dawn/native/CacheKey.h +++ b/src/dawn/native/CacheKey.h @@ -15,6 +15,7 @@ #ifndef SRC_DAWN_NATIVE_CACHEKEY_H_ #define SRC_DAWN_NATIVE_CACHEKEY_H_ +#include #include #include #include @@ -85,6 +86,41 @@ namespace dawn::native { } }; + // Specialized overload for bitsets that are smaller than 64. + template + class CacheKeySerializer, std::enable_if_t<(N <= 64)>> { + public: + static void Serialize(CacheKey* key, const std::bitset& t) { + key->Record(t.to_ullong()); + } + }; + + // Specialized overload for bitsets since using the built-in to_ullong have a size limit. + template + class CacheKeySerializer, std::enable_if_t<(N > 64)>> { + public: + static void Serialize(CacheKey* key, const std::bitset& t) { + // Serializes the bitset into series of uint8_t, along with recording the size. + static_assert(N > 0); + key->Record(static_cast(N)); + uint8_t value = 0; + for (size_t i = 0; i < N; i++) { + value <<= 1; + // Explicitly convert to numeric since MSVC doesn't like mixing of bools. + value |= t[i] ? 1 : 0; + if (i % 8 == 7) { + // Whenever we fill an 8 bit value, record it and zero it out. + key->Record(value); + value = 0; + } + } + // Serialize the last value if we are not a multiple of 8. + if (N % 8 != 0) { + key->Record(value); + } + } + }; + // Specialized overload for enums. template class CacheKeySerializer>> { diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp index 7576c1b477..22dd2b90d7 100644 --- a/src/dawn/native/Device.cpp +++ b/src/dawn/native/Device.cpp @@ -177,6 +177,9 @@ namespace dawn::native { : mInstance(adapter->GetInstance()), mAdapter(adapter), mNextPipelineCompatibilityToken(1) { ASSERT(descriptor != nullptr); + AdapterProperties adapterProperties; + adapter->APIGetProperties(&adapterProperties); + const DawnTogglesDeviceDescriptor* togglesDesc = nullptr; FindInChain(descriptor->nextInChain, &togglesDesc); if (togglesDesc != nullptr) { @@ -184,10 +187,11 @@ namespace dawn::native { } ApplyFeatures(descriptor); + DawnCacheDeviceDescriptor defaultCacheDesc = {}; const DawnCacheDeviceDescriptor* cacheDesc = nullptr; FindInChain(descriptor->nextInChain, &cacheDesc); - if (cacheDesc != nullptr) { - mCacheIsolationKey = cacheDesc->isolationKey; + if (cacheDesc == nullptr) { + cacheDesc = &defaultCacheDesc; } if (descriptor->requiredLimits != nullptr) { @@ -202,6 +206,12 @@ namespace dawn::native { if (descriptor->label != nullptr && strlen(descriptor->label) != 0) { mLabel = descriptor->label; } + + // Record the cache key from the properties. Note that currently, if a new extension + // descriptor is added (and probably handled here), the cache key recording needs to be + // updated. + mDeviceCacheKey.Record(adapterProperties, mEnabledFeatures.featuresBitSet, + mEnabledToggles.toggleBitset, cacheDesc); } DeviceBase::DeviceBase() : mState(State::Alive) { @@ -1789,8 +1799,8 @@ namespace dawn::native { return PipelineCompatibilityToken(mNextPipelineCompatibilityToken++); } - const std::string& DeviceBase::GetCacheIsolationKey() const { - return mCacheIsolationKey; + const CacheKey& DeviceBase::GetCacheKey() const { + return mDeviceCacheKey; } const std::string& DeviceBase::GetLabel() const { diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h index fbc81d8a0f..bbffcc9b52 100644 --- a/src/dawn/native/Device.h +++ b/src/dawn/native/Device.h @@ -15,6 +15,7 @@ #ifndef SRC_DAWN_NATIVE_DEVICE_H_ #define SRC_DAWN_NATIVE_DEVICE_H_ +#include "dawn/native/CacheKey.h" #include "dawn/native/Commands.h" #include "dawn/native/ComputePipeline.h" #include "dawn/native/Error.h" @@ -370,7 +371,7 @@ namespace dawn::native { PipelineCompatibilityToken GetNextPipelineCompatibilityToken(); - const std::string& GetCacheIsolationKey() const; + const CacheKey& GetCacheKey() const; const std::string& GetLabel() const; void APISetLabel(const char* label); void APIDestroy(); @@ -547,7 +548,7 @@ namespace dawn::native { std::unique_ptr mCallbackTaskManager; std::unique_ptr mWorkerTaskPool; std::string mLabel; - std::string mCacheIsolationKey = ""; + CacheKey mDeviceCacheKey; }; } // namespace dawn::native diff --git a/src/dawn/tests/unittests/native/DeviceCreationTests.cpp b/src/dawn/tests/unittests/native/DeviceCreationTests.cpp index 20c0fac3a1..5c65734a54 100644 --- a/src/dawn/tests/unittests/native/DeviceCreationTests.cpp +++ b/src/dawn/tests/unittests/native/DeviceCreationTests.cpp @@ -86,41 +86,55 @@ namespace { } TEST_F(DeviceCreationTest, CreateDeviceWithCacheSuccess) { - // Default device descriptor should have an empty cache isolation key. + // Default device descriptor should have the same cache key as a device descriptor with a + // default cache descriptor. { wgpu::DeviceDescriptor desc = {}; - wgpu::Device device = adapter.CreateDevice(&desc); - EXPECT_NE(device, nullptr); + wgpu::Device device1 = adapter.CreateDevice(&desc); + EXPECT_NE(device1, nullptr); - EXPECT_THAT(dawn::native::FromAPI(device.Get())->GetCacheIsolationKey(), - testing::StrEq("")); - } - // Device descriptor with empty cache descriptor should have an empty cache isolation key. - { - wgpu::DeviceDescriptor desc = {}; wgpu::DawnCacheDeviceDescriptor cacheDesc = {}; desc.nextInChain = &cacheDesc; + wgpu::Device device2 = adapter.CreateDevice(&desc); - wgpu::Device device = adapter.CreateDevice(&desc); - EXPECT_NE(device, nullptr); - - EXPECT_THAT(dawn::native::FromAPI(device.Get())->GetCacheIsolationKey(), - testing::StrEq("")); + EXPECT_EQ(dawn::native::FromAPI(device1.Get())->GetCacheKey(), + dawn::native::FromAPI(device2.Get())->GetCacheKey()); } - // Specified cache isolation key should be retained. + // Default device descriptor should not have the same cache key as a device descriptor with + // a non-default cache descriptor. { wgpu::DeviceDescriptor desc = {}; + wgpu::Device device1 = adapter.CreateDevice(&desc); + EXPECT_NE(device1, nullptr); + wgpu::DawnCacheDeviceDescriptor cacheDesc = {}; desc.nextInChain = &cacheDesc; - const char* isolationKey = "isolation key"; cacheDesc.isolationKey = isolationKey; + wgpu::Device device2 = adapter.CreateDevice(&desc); + EXPECT_NE(device2, nullptr); - wgpu::Device device = adapter.CreateDevice(&desc); - EXPECT_NE(device, nullptr); + EXPECT_NE(dawn::native::FromAPI(device1.Get())->GetCacheKey(), + dawn::native::FromAPI(device2.Get())->GetCacheKey()); + } + // Two non-default cache descriptors should not have the same cache key. + { + wgpu::DawnCacheDeviceDescriptor cacheDesc = {}; + const char* isolationKey1 = "isolation key 1"; + const char* isolationKey2 = "isolation key 2"; + wgpu::DeviceDescriptor desc = {}; + desc.nextInChain = &cacheDesc; - EXPECT_THAT(dawn::native::FromAPI(device.Get())->GetCacheIsolationKey(), - testing::StrEq(isolationKey)); + cacheDesc.isolationKey = isolationKey1; + wgpu::Device device1 = adapter.CreateDevice(&desc); + EXPECT_NE(device1, nullptr); + + cacheDesc.isolationKey = isolationKey2; + wgpu::Device device2 = adapter.CreateDevice(&desc); + EXPECT_NE(device2, nullptr); + + EXPECT_NE(dawn::native::FromAPI(device1.Get())->GetCacheKey(), + dawn::native::FromAPI(device2.Get())->GetCacheKey()); } }