Merge "Don't use su to when calling am or dumpsys." into nyc-dev
diff --git a/cmds/dumpstate/bugreport-format.md b/cmds/dumpstate/bugreport-format.md
index fc43250..484f97f 100644
--- a/cmds/dumpstate/bugreport-format.md
+++ b/cmds/dumpstate/bugreport-format.md
@@ -41,6 +41,8 @@
under the `FS` folder. For example, a `/dirA/dirB/fileC` file in the device
would generate a `FS/dirA/dirB/fileC` entry in the zip file.
+When systrace is enabled, the zip file will contain a `systrace.txt` file as well.
+
The flat file also has some minor changes:
- Tombstone files were removed and added to the zip file.
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 6e45ff1..5119061 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -62,6 +62,7 @@
static std::set<std::string> mount_points;
void add_mountinfo();
static bool add_zip_entry(const std::string& entry_name, const std::string& entry_path);
+static bool add_zip_entry_from_fd(const std::string& entry_name, int fd);
#define PSTORE_LAST_KMSG "/sys/fs/pstore/console-ramoops"
@@ -89,7 +90,7 @@
* See bugreport-format.txt for more info.
*/
// TODO: change to "v1" before final N build
-static std::string VERSION_DEFAULT = "v1-dev2";
+static std::string VERSION_DEFAULT = "v1-dev3";
/* gets the tombstone data, according to the bugreport type: if zipped gets all tombstones,
* otherwise gets just those modified in the last half an hour. */
@@ -167,6 +168,42 @@
closedir(d);
}
+static void dump_systrace() {
+ if (!zip_writer) {
+ MYLOGD("Not dumping systrace because zip_writer is not set\n");
+ return;
+ }
+ const char* path = "/sys/kernel/debug/tracing/tracing_on";
+ long int is_tracing;
+ if (read_file_as_long(path, &is_tracing)) {
+ return; // error already logged
+ }
+ if (is_tracing <= 0) {
+ MYLOGD("Skipping systrace because '%s' content is '%ld'\n", path, is_tracing);
+ return;
+ }
+
+ DurationReporter duration_reporter("SYSTRACE", nullptr);
+ // systrace output can be many MBs, so we need to redirect its stdout straigh to the zip file by
+ // forking and using a pipe.
+ int pipefd[2];
+ pipe(pipefd);
+ if (fork() == 0) {
+ close(pipefd[0]); // close reading end in the child
+ dup2(pipefd[1], STDOUT_FILENO); // send stdout to the pipe
+ dup2(pipefd[1], STDERR_FILENO); // send stderr to the pipe
+ close(pipefd[1]); // this descriptor is no longer needed
+
+ // TODO: ideally it should use run_command, but it doesn't work well with pipes.
+ // The drawback of calling execl directly is that we're not timing out if it hangs.
+ MYLOGD("Running '/system/bin/atrace --async_dump', which can take several seconds");
+ execl("/system/bin/atrace", "/system/bin/atrace", "--async_dump", nullptr);
+ } else {
+ close(pipefd[1]); // close the write end of the pipe in the parent
+ add_zip_entry_from_fd("systrace.txt", pipefd[0]); // write output to zip file
+ }
+}
+
static bool skip_not_stat(const char *path) {
static const char stat[] = "/stat";
size_t len = strlen(path);
@@ -1208,11 +1245,15 @@
// duration is logged into MYLOG instead.
print_header(version);
+ // Dumps systrace right away, otherwise it will be filled with unnecessary events.
+ dump_systrace();
+
// Invoking the following dumpsys calls before dump_traces() to try and
// keep the system stats as close to its initial state as possible.
run_command_as_shell("DUMPSYS MEMINFO", 30, "dumpsys", "meminfo", "-a", NULL);
run_command_as_shell("DUMPSYS CPUINFO", 30, "dumpsys", "cpuinfo", "-a", NULL);
+
/* collect stack traces from Dalvik and native processes (needs root) */
dump_traces_path = dump_traces();
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 72cd22d..02d1256 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -85,6 +85,9 @@
/* prints the contents of a file */
int dump_file(const char *title, const char *path);
+/* saves the the contents of a file as a long */
+int read_file_as_long(const char *path, long int *output);
+
/* prints the contents of the fd
* fd must have been opened with the flag O_NONBLOCK.
*/
diff --git a/cmds/dumpstate/utils.cpp b/cmds/dumpstate/utils.cpp
index 262778f..89c9653 100644
--- a/cmds/dumpstate/utils.cpp
+++ b/cmds/dumpstate/utils.cpp
@@ -395,7 +395,7 @@
sprintf(title, "SHOW MAP %d (%s)", pid, name);
sprintf(arg, "%d", pid);
- run_command(title, 10, SU_PATH, "root", "showmap", arg, NULL);
+ run_command(title, 10, SU_PATH, "root", "showmap", "-q", arg, NULL);
}
static int _dump_file_from_fd(const char *title, const char *path, int fd) {
@@ -475,6 +475,27 @@
return _dump_file_from_fd(title, path, fd);
}
+int read_file_as_long(const char *path, long int *output) {
+ int fd = TEMP_FAILURE_RETRY(open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC));
+ if (fd < 0) {
+ int err = errno;
+ MYLOGE("Error opening file descriptor for %s: %s\n", path, strerror(err));
+ return -1;
+ }
+ char buffer[50];
+ ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd, buffer, sizeof(buffer)));
+ if (bytes_read == -1) {
+ MYLOGE("Error reading file %s: %s\n", path, strerror(errno));
+ return -2;
+ }
+ if (bytes_read == 0) {
+ MYLOGE("File %s is empty\n", path);
+ return -3;
+ }
+ *output = atoi(buffer);
+ return 0;
+}
+
/* calls skip to gate calling dump_from_fd recursively
* in the specified directory. dump_from_fd defaults to
* dump_file_from_fd above when set to NULL. skip defaults
@@ -716,7 +737,6 @@
execvp(command, (char**) args);
// execvp's result will be handled after waitpid_with_timeout() below...
- _exit(-1); // ...but it doesn't hurt to force exit, just in case
}
/* handle parent case */
diff --git a/include/android/sensor.h b/include/android/sensor.h
index f2647be..5a61213 100644
--- a/include/android/sensor.h
+++ b/include/android/sensor.h
@@ -393,6 +393,13 @@
/*****************************************************************************/
/**
+ * Enable the selected sensor with a specified sampling period and max batch report latency.
+ * Returns a negative error code on failure.
+ */
+int ASensorEventQueue_registerSensor(ASensorEventQueue* queue, ASensor const* sensor,
+ int32_t samplingPeriodUs, int maxBatchReportLatencyUs);
+
+/**
* Enable the selected sensor. Returns a negative error code on failure.
*/
int ASensorEventQueue_enableSensor(ASensorEventQueue* queue, ASensor const* sensor);
diff --git a/include/binder/MemoryDealer.h b/include/binder/MemoryDealer.h
index aa415d5..60a624c 100644
--- a/include/binder/MemoryDealer.h
+++ b/include/binder/MemoryDealer.h
@@ -41,6 +41,9 @@
virtual void deallocate(size_t offset);
virtual void dump(const char* what) const;
+ // allocations are aligned to some value. return that value so clients can account for it.
+ static size_t getAllocationAlignment();
+
sp<IMemoryHeap> getMemoryHeap() const { return heap(); }
protected:
diff --git a/include/gui/BufferQueueCore.h b/include/gui/BufferQueueCore.h
index acc8c4b..1b950ab 100644
--- a/include/gui/BufferQueueCore.h
+++ b/include/gui/BufferQueueCore.h
@@ -292,9 +292,6 @@
bool mAsyncMode;
// mSingleBufferMode indicates whether or not single buffer mode is enabled.
- // In single buffer mode, the last buffer that was dequeued is cached and
- // returned to all calls to dequeueBuffer and acquireBuffer. This allows the
- // consumer and producer to access the same buffer simultaneously.
bool mSingleBufferMode;
// When single buffer mode is enabled, this indicates whether the consumer
diff --git a/include/gui/IGraphicBufferProducer.h b/include/gui/IGraphicBufferProducer.h
index f6b4230..fee7c63 100644
--- a/include/gui/IGraphicBufferProducer.h
+++ b/include/gui/IGraphicBufferProducer.h
@@ -526,9 +526,10 @@
// Used to enable/disable single buffer mode.
//
- // In single buffer mode the last buffer that was dequeued will be cached
- // and returned to all calls to dequeueBuffer and acquireBuffer. This allows
- // the producer and consumer to simultaneously access the same buffer.
+ // When single buffer mode is enabled the first buffer that is queued or
+ // dequeued will be cached and returned to all subsequent calls to
+ // dequeueBuffer and acquireBuffer. This allows the producer and consumer to
+ // simultaneously access the same buffer.
virtual status_t setSingleBufferMode(bool singleBufferMode) = 0;
// Used to enable/disable auto-refresh.
diff --git a/include/hardware_properties/HardwarePropertiesManager.h b/include/hardware_properties/HardwarePropertiesManager.h
deleted file mode 100644
index 13f2b99..0000000
--- a/include/hardware_properties/HardwarePropertiesManager.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * 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 ANDROID_HARDWAREPROPERTIESMANAGER_H
-#define ANDROID_HARDWAREPROPERTIESMANAGER_H
-
-namespace android {
-
-// must be kept in sync with definitions in HardwarePropertiesManager.java
-enum {
- DEVICE_TEMPERATURE_CPU = 0,
- DEVICE_TEMPERATURE_GPU = 1,
- DEVICE_TEMPERATURE_BATTERY = 2,
-};
-
-}; // namespace android
-
-#endif // ANDROID_HARDWAREPROPERTIESMANAGER_H
diff --git a/include/media/openmax/OMX_Audio.h b/include/media/openmax/OMX_Audio.h
index a0cbd3b..d8bee76 100644
--- a/include/media/openmax/OMX_Audio.h
+++ b/include/media/openmax/OMX_Audio.h
@@ -178,7 +178,7 @@
OMX_VERSIONTYPE nVersion; /**< OMX specification version information */
OMX_U32 nPortIndex; /**< port that this structure applies to */
OMX_U32 nChannels; /**< Number of channels (e.g. 2 for stereo) */
- OMX_NUMERICALDATATYPE eNumData; /**< indicates PCM data as signed or unsigned */
+ OMX_NUMERICALDATATYPE eNumData; /**< indicates PCM data as signed, unsigned or floating pt. */
OMX_ENDIANTYPE eEndian; /**< indicates PCM data as little or big endian */
OMX_BOOL bInterleaved; /**< True for normal interleaved data; false for
non-interleaved data (e.g. block data) */
diff --git a/include/media/openmax/OMX_Types.h b/include/media/openmax/OMX_Types.h
index 5afaba0..515e002 100644
--- a/include/media/openmax/OMX_Types.h
+++ b/include/media/openmax/OMX_Types.h
@@ -280,12 +280,18 @@
/** The OMX_NUMERICALDATATYPE enumeration is used to indicate if data
- is signed or unsigned
+ is signed, unsigned or floating point (Android extension).
+
+ Android floating point support policy:
+ If component does not support floating point raw audio, it can reset
+ configuration to signed 16-bit integer (support for which is required.)
+ nBitsPerSample will be set to 32 for float data.
*/
typedef enum OMX_NUMERICALDATATYPE
{
OMX_NumericalDataSigned, /**< signed data */
OMX_NumericalDataUnsigned, /**< unsigned data */
+ OMX_NumericalDataFloat = 0x7F000001, /**< floating point data */
OMX_NumercialDataMax = 0x7FFFFFFF
} OMX_NUMERICALDATATYPE;
diff --git a/include/powermanager/PowerManager.h b/include/powermanager/PowerManager.h
index cbddc11..3268b45 100644
--- a/include/powermanager/PowerManager.h
+++ b/include/powermanager/PowerManager.h
@@ -28,8 +28,9 @@
USER_ACTIVITY_EVENT_OTHER = 0,
USER_ACTIVITY_EVENT_BUTTON = 1,
USER_ACTIVITY_EVENT_TOUCH = 2,
+ USER_ACTIVITY_EVENT_ACCESSIBILITY = 3,
- USER_ACTIVITY_EVENT_LAST = USER_ACTIVITY_EVENT_TOUCH, // Last valid event code.
+ USER_ACTIVITY_EVENT_LAST = USER_ACTIVITY_EVENT_ACCESSIBILITY, // Last valid event code.
};
}; // namespace android
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index 1f6bda2..1cbcfe4 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -1089,8 +1089,16 @@
<< reinterpret_cast<const size_t*>(tr.data.ptr.offsets) << endl;
}
if (tr.target.ptr) {
- sp<BBinder> b((BBinder*)tr.cookie);
- error = b->transact(tr.code, buffer, &reply, tr.flags);
+ // We only have a weak reference on the target object, so we must first try to
+ // safely acquire a strong reference before doing anything else with it.
+ if (reinterpret_cast<RefBase::weakref_type*>(
+ tr.target.ptr)->attemptIncStrong(this)) {
+ error = reinterpret_cast<BBinder*>(tr.cookie)->transact(tr.code, buffer,
+ &reply, tr.flags);
+ reinterpret_cast<BBinder*>(tr.cookie)->decStrong(this);
+ } else {
+ error = UNKNOWN_TRANSACTION;
+ }
} else {
error = the_context_object->transact(tr.code, buffer, &reply, tr.flags);
diff --git a/libs/binder/MemoryDealer.cpp b/libs/binder/MemoryDealer.cpp
index 8739625..51eac11 100644
--- a/libs/binder/MemoryDealer.cpp
+++ b/libs/binder/MemoryDealer.cpp
@@ -135,6 +135,8 @@
void dump(const char* what) const;
void dump(String8& res, const char* what) const;
+ static size_t getAllocationAlignment() { return kMemoryAlign; }
+
private:
struct chunk_t {
@@ -264,6 +266,12 @@
return mAllocator;
}
+// static
+size_t MemoryDealer::getAllocationAlignment()
+{
+ return SimpleBestFitAllocator::getAllocationAlignment();
+}
+
// ----------------------------------------------------------------------------
// align all the memory blocks on a cache-line boundary
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index e9d0654..4fd8b89 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -761,6 +761,14 @@
return BAD_VALUE;
}
+ // If single buffer mode has just been enabled, cache the slot of the
+ // first buffer that is queued and mark it as the shared buffer.
+ if (mCore->mSingleBufferMode && mCore->mSingleBufferSlot ==
+ BufferQueueCore::INVALID_BUFFER_SLOT) {
+ mCore->mSingleBufferSlot = slot;
+ mSlots[slot].mBufferState.mShared = true;
+ }
+
BQ_LOGV("queueBuffer: slot=%d/%" PRIu64 " time=%" PRIu64 " dataSpace=%d"
" crop=[%d,%d,%d,%d] transform=%#x scale=%s",
slot, mCore->mFrameCounter + 1, timestamp, dataSpace,
diff --git a/libs/gui/IGraphicBufferConsumer.cpp b/libs/gui/IGraphicBufferConsumer.cpp
index a75569f..cb1ad35 100644
--- a/libs/gui/IGraphicBufferConsumer.cpp
+++ b/libs/gui/IGraphicBufferConsumer.cpp
@@ -338,7 +338,7 @@
}
case GET_RELEASED_BUFFERS: {
CHECK_INTERFACE(IGraphicBufferConsumer, data, reply);
- uint64_t slotMask;
+ uint64_t slotMask = 0;
status_t result = getReleasedBuffers(&slotMask);
reply->writeInt64(static_cast<int64_t>(slotMask));
reply->writeInt32(result);
diff --git a/libs/gui/IGraphicBufferProducer.cpp b/libs/gui/IGraphicBufferProducer.cpp
index c66694d..3101f3a 100644
--- a/libs/gui/IGraphicBufferProducer.cpp
+++ b/libs/gui/IGraphicBufferProducer.cpp
@@ -513,6 +513,7 @@
QueueBufferOutput* const output =
reinterpret_cast<QueueBufferOutput *>(
reply->writeInplace(sizeof(QueueBufferOutput)));
+ memset(output, 0, sizeof(QueueBufferOutput));
status_t res = connect(listener, api, producerControlledByApp, output);
reply->writeInt32(res);
return NO_ERROR;
diff --git a/libs/gui/SensorEventQueue.cpp b/libs/gui/SensorEventQueue.cpp
index 4b7986e..5983a6c 100644
--- a/libs/gui/SensorEventQueue.cpp
+++ b/libs/gui/SensorEventQueue.cpp
@@ -125,7 +125,7 @@
}
status_t SensorEventQueue::enableSensor(Sensor const* sensor) const {
- return mSensorEventConnection->enableDisable(sensor->getHandle(), true, 0, 0, false);
+ return mSensorEventConnection->enableDisable(sensor->getHandle(), true, us2ns(200000), 0, false);
}
status_t SensorEventQueue::disableSensor(Sensor const* sensor) const {
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index b6af166..7790762 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -661,6 +661,57 @@
}
}
+TEST_F(BufferQueueTest, TestSingleBufferModeUsingAlreadyDequeuedBuffer) {
+ createBufferQueue();
+ sp<DummyConsumer> dc(new DummyConsumer);
+ ASSERT_EQ(OK, mConsumer->consumerConnect(dc, true));
+ IGraphicBufferProducer::QueueBufferOutput output;
+ ASSERT_EQ(OK, mProducer->connect(new DummyProducerListener,
+ NATIVE_WINDOW_API_CPU, true, &output));
+
+ // Dequeue a buffer
+ int singleSlot;
+ sp<Fence> fence;
+ sp<GraphicBuffer> buffer;
+ ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
+ mProducer->dequeueBuffer(&singleSlot, &fence, 0, 0, 0, 0));
+ ASSERT_EQ(OK, mProducer->requestBuffer(singleSlot, &buffer));
+
+ // Enable single buffer mode
+ ASSERT_EQ(OK, mProducer->setSingleBufferMode(true));
+
+ // Queue the buffer
+ IGraphicBufferProducer::QueueBufferInput input(0, false,
+ HAL_DATASPACE_UNKNOWN, Rect(0, 0, 1, 1),
+ NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, Fence::NO_FENCE);
+ ASSERT_EQ(OK, mProducer->queueBuffer(singleSlot, input, &output));
+
+ // Repeatedly queue and dequeue a buffer from the producer side, it should
+ // always return the same one. And we won't run out of buffers because it's
+ // always the same one and because async mode gets enabled.
+ int slot;
+ for (int i = 0; i < 5; i++) {
+ ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0));
+ ASSERT_EQ(singleSlot, slot);
+ ASSERT_EQ(OK, mProducer->queueBuffer(singleSlot, input, &output));
+ }
+
+ // acquire the buffer
+ BufferItem item;
+ ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
+ ASSERT_EQ(singleSlot, item.mSlot);
+ testBufferItem(input, item);
+ ASSERT_EQ(true, item.mQueuedBuffer);
+ ASSERT_EQ(false, item.mAutoRefresh);
+
+ ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
+ EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+
+ // attempt to acquire a second time should return no buffer available
+ ASSERT_EQ(IGraphicBufferConsumer::NO_BUFFER_AVAILABLE,
+ mConsumer->acquireBuffer(&item, 0));
+}
+
TEST_F(BufferQueueTest, TestTimeouts) {
createBufferQueue();
sp<DummyConsumer> dc(new DummyConsumer);
diff --git a/opengl/libs/EGL/eglApi.cpp b/opengl/libs/EGL/eglApi.cpp
index 794a7e5..e7703d8 100644
--- a/opengl/libs/EGL/eglApi.cpp
+++ b/opengl/libs/EGL/eglApi.cpp
@@ -1196,8 +1196,10 @@
egl_surface_t const * const s = get_surface(surface);
if (attribute == EGL_FRONT_BUFFER_AUTO_REFRESH_ANDROID) {
- return (native_window_set_auto_refresh(s->win.get(),
- value ? true : false)) ? EGL_TRUE : EGL_FALSE;
+ int err = native_window_set_auto_refresh(s->win.get(),
+ value ? true : false);
+ return (err == NO_ERROR) ? EGL_TRUE :
+ setError(EGL_BAD_SURFACE, EGL_FALSE);
}
if (s->cnx->egl.eglSurfaceAttrib) {
diff --git a/vulkan/libvulkan/loader.cpp b/vulkan/libvulkan/loader.cpp
index d5cc280..eba58c6 100644
--- a/vulkan/libvulkan/loader.cpp
+++ b/vulkan/libvulkan/loader.cpp
@@ -265,6 +265,7 @@
const VkAllocationCallbacks* alloc;
uint32_t num_physical_devices;
+ VkPhysicalDevice physical_devices_top[kMaxPhysicalDevices];
VkPhysicalDevice physical_devices[kMaxPhysicalDevices];
DeviceExtensionSet physical_device_driver_extensions[kMaxPhysicalDevices];
@@ -1086,7 +1087,7 @@
ALOGV(" no layer");
Instance& instance = GetDispatchParent(gpu);
size_t gpu_idx = 0;
- while (instance.physical_devices[gpu_idx] != gpu)
+ while (instance.physical_devices_top[gpu_idx] != gpu)
gpu_idx++;
const DeviceExtensionSet driver_extensions =
instance.physical_device_driver_extensions[gpu_idx];
@@ -1253,6 +1254,24 @@
DestroyInstance(instance, allocator);
return VK_ERROR_INITIALIZATION_FAILED;
}
+
+ // Capture the physical devices from the top of the
+ // chain in case it has been wrapped by a layer.
+ uint32_t num_physical_devices = 0;
+ result = instance_dispatch.EnumeratePhysicalDevices(
+ local_instance, &num_physical_devices, nullptr);
+ if (result != VK_SUCCESS) {
+ DestroyInstance(instance, allocator);
+ return VK_ERROR_INITIALIZATION_FAILED;
+ }
+ num_physical_devices = std::min(num_physical_devices, kMaxPhysicalDevices);
+ result = instance_dispatch.EnumeratePhysicalDevices(
+ local_instance, &num_physical_devices,
+ instance->physical_devices_top);
+ if (result != VK_SUCCESS) {
+ DestroyInstance(instance, allocator);
+ return VK_ERROR_INITIALIZATION_FAILED;
+ }
*instance_out = local_instance;
if (enable_callback) {
@@ -1342,10 +1361,6 @@
return result;
}
- size_t gpu_idx = 0;
- while (instance.physical_devices[gpu_idx] != gpu)
- gpu_idx++;
-
uint32_t activated_layers = 0;
VkLayerDeviceCreateInfo chain_info;
VkLayerDeviceLink* layer_device_link_info = nullptr;