summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKeith Packard <keithp@keithp.com>2017-05-09 22:18:44 -0700
committerKeith Packard <keithp@keithp.com>2017-05-09 22:25:03 -0700
commita695f807289cec3c29caf5cc7ddf39eea8167f66 (patch)
treef5f5cec2b79d9a12cf6627d9b771513f9f96cdd6
Import bare cube demo
-rw-r--r--Makefile34
-rw-r--r--cube.c4111
-rw-r--r--cube.frag30
-rw-r--r--cube.vert40
-rw-r--r--gettime.h74
-rw-r--r--jesse.ppm5
-rw-r--r--linmath.h501
7 files changed, 4795 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..475c788
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,34 @@
+SRCS=\
+ cube.c
+
+OBJS=$(SRCS:.c=.o)
+
+INCS=\
+ gettime.h\
+ linmath.h
+
+LIBS=-L/local/xorg/lib -lvulkan -lxcb -lm
+
+TARGET=cube
+
+GLSV=glslangValidator
+
+SPV=cube-vert.spv cube-frag.spv
+
+CFLAGS=-O0 -g -DVK_USE_PLATFORM_XCB_KHR -I/local/xorg/include
+
+all: $(TARGET) $(SPV)
+
+$(TARGET): $(OBJS)
+ $(CC) $(CFLAGS) -o $@ $(OBJS) $(LIBS)
+
+$(OBJS): $(INCS)
+
+cube-vert.spv: cube.vert
+ $(GLSV) -V -o $@ cube.vert
+
+cube-frag.spv: cube.frag
+ $(GLSV) -V -o $@ cube.frag
+
+clean:
+ rm -f $(TARGET) $(OBJS) $(SPV)
diff --git a/cube.c b/cube.c
new file mode 100644
index 0000000..4db3d91
--- /dev/null
+++ b/cube.c
@@ -0,0 +1,4111 @@
+/*
+ * Copyright (c) 2015-2016 The Khronos Group Inc.
+ * Copyright (c) 2015-2016 Valve Corporation
+ * Copyright (c) 2015-2016 LunarG, Inc.
+ *
+ * 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.
+ *
+ * Author: Chia-I Wu <olv@lunarg.com>
+ * Author: Courtney Goeltzenleuchter <courtney@LunarG.com>
+ * Author: Ian Elliott <ian@LunarG.com>
+ * Author: Ian Elliott <ianelliott@google.com>
+ * Author: Jon Ashburn <jon@lunarg.com>
+ * Author: Gwan-gyeong Mun <elongbug@gmail.com>
+ * Author: Tony Barbour <tony@LunarG.com>
+ * Author: Bill Hollings <bill.hollings@brenwill.com>
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <signal.h>
+#if defined(VK_USE_PLATFORM_XLIB_KHR) || defined(VK_USE_PLATFORM_XCB_KHR)
+#include <X11/Xutil.h>
+#endif
+
+#ifdef _WIN32
+#pragma comment(linker, "/subsystem:windows")
+#define APP_NAME_STR_LEN 80
+#endif // _WIN32
+
+#if defined(VK_USE_PLATFORM_MIR_KHR)
+#warning "Cube does not have code for Mir at this time"
+#endif
+
+#ifdef ANDROID
+#include "vulkan_wrapper.h"
+#else
+#include <vulkan/vulkan.h>
+#endif
+
+#include <vulkan/vk_sdk_platform.h>
+#include "linmath.h"
+
+#include "gettime.h"
+#include "inttypes.h"
+#define MILLION 1000000L
+#define BILLION 1000000000L
+
+#define DEMO_TEXTURE_COUNT 1
+#define APP_SHORT_NAME "cube"
+#define APP_LONG_NAME "The Vulkan Cube Demo Program"
+
+// Allow a maximum of two outstanding presentation operations.
+#define FRAME_LAG 2
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
+
+#if defined(NDEBUG) && defined(__GNUC__)
+#define U_ASSERT_ONLY __attribute__((unused))
+#else
+#define U_ASSERT_ONLY
+#endif
+
+#if defined(__GNUC__)
+#define UNUSED __attribute__((unused))
+#else
+#define UNUSED
+#endif
+
+#ifdef _WIN32
+bool in_callback = false;
+#define ERR_EXIT(err_msg, err_class) \
+ do { \
+ if (!demo->suppress_popups) MessageBox(NULL, err_msg, err_class, MB_OK); \
+ exit(1); \
+ } while (0)
+void DbgMsg(char *fmt, ...) {
+ va_list va;
+ va_start(va, fmt);
+ printf(fmt, va);
+ fflush(stdout);
+ va_end(va);
+}
+
+#elif defined __ANDROID__
+#include <android/log.h>
+#define ERR_EXIT(err_msg, err_class) \
+ do { \
+ ((void)__android_log_print(ANDROID_LOG_INFO, "Cube", err_msg)); \
+ exit(1); \
+ } while (0)
+#ifdef VARARGS_WORKS_ON_ANDROID
+void DbgMsg(const char *fmt, ...) {
+ va_list va;
+ va_start(va, fmt);
+ __android_log_print(ANDROID_LOG_INFO, "Cube", fmt, va);
+ va_end(va);
+}
+#else // VARARGS_WORKS_ON_ANDROID
+#define DbgMsg(fmt, ...) \
+ do { \
+ ((void)__android_log_print(ANDROID_LOG_INFO, "Cube", fmt, ##__VA_ARGS__)); \
+ } while (0)
+#endif // VARARGS_WORKS_ON_ANDROID
+#else
+#define ERR_EXIT(err_msg, err_class) \
+ do { \
+ printf("%s\n", err_msg); \
+ fflush(stdout); \
+ exit(1); \
+ } while (0)
+void DbgMsg(char *fmt, ...) {
+ va_list va;
+ va_start(va, fmt);
+ printf(fmt, va);
+ fflush(stdout);
+ va_end(va);
+}
+#endif
+
+#define GET_INSTANCE_PROC_ADDR(inst, entrypoint) \
+ { \
+ demo->fp##entrypoint = (PFN_vk##entrypoint)vkGetInstanceProcAddr(inst, "vk" #entrypoint); \
+ if (demo->fp##entrypoint == NULL) { \
+ ERR_EXIT("vkGetInstanceProcAddr failed to find vk" #entrypoint, "vkGetInstanceProcAddr Failure"); \
+ } \
+ }
+
+static PFN_vkGetDeviceProcAddr g_gdpa = NULL;
+
+#define GET_DEVICE_PROC_ADDR(dev, entrypoint) \
+ { \
+ if (!g_gdpa) g_gdpa = (PFN_vkGetDeviceProcAddr)vkGetInstanceProcAddr(demo->inst, "vkGetDeviceProcAddr"); \
+ demo->fp##entrypoint = (PFN_vk##entrypoint)g_gdpa(dev, "vk" #entrypoint); \
+ if (demo->fp##entrypoint == NULL) { \
+ ERR_EXIT("vkGetDeviceProcAddr failed to find vk" #entrypoint, "vkGetDeviceProcAddr Failure"); \
+ } \
+ }
+
+/*
+ * structure to track all objects related to a texture.
+ */
+struct texture_object {
+ VkSampler sampler;
+
+ VkImage image;
+ VkImageLayout imageLayout;
+
+ VkMemoryAllocateInfo mem_alloc;
+ VkDeviceMemory mem;
+ VkImageView view;
+ int32_t tex_width, tex_height;
+};
+
+static char *tex_files[] = {"jesse.ppm"};
+
+static int validation_error = 0;
+
+struct vktexcube_vs_uniform {
+ // Must start with MVP
+ float mvp[4][4];
+ float position[12 * 3][4];
+ float attr[12 * 3][4];
+};
+
+//--------------------------------------------------------------------------------------
+// Mesh and VertexFormat Data
+//--------------------------------------------------------------------------------------
+// clang-format off
+static const float g_vertex_buffer_data[] = {
+ -1.0f,-1.0f,-1.0f, // -X side
+ -1.0f,-1.0f, 1.0f,
+ -1.0f, 1.0f, 1.0f,
+ -1.0f, 1.0f, 1.0f,
+ -1.0f, 1.0f,-1.0f,
+ -1.0f,-1.0f,-1.0f,
+
+ -1.0f,-1.0f,-1.0f, // -Z side
+ 1.0f, 1.0f,-1.0f,
+ 1.0f,-1.0f,-1.0f,
+ -1.0f,-1.0f,-1.0f,
+ -1.0f, 1.0f,-1.0f,
+ 1.0f, 1.0f,-1.0f,
+
+ -1.0f,-1.0f,-1.0f, // -Y side
+ 1.0f,-1.0f,-1.0f,
+ 1.0f,-1.0f, 1.0f,
+ -1.0f,-1.0f,-1.0f,
+ 1.0f,-1.0f, 1.0f,
+ -1.0f,-1.0f, 1.0f,
+
+ -1.0f, 1.0f,-1.0f, // +Y side
+ -1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f,
+ -1.0f, 1.0f,-1.0f,
+ 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f,-1.0f,
+
+ 1.0f, 1.0f,-1.0f, // +X side
+ 1.0f, 1.0f, 1.0f,
+ 1.0f,-1.0f, 1.0f,
+ 1.0f,-1.0f, 1.0f,
+ 1.0f,-1.0f,-1.0f,
+ 1.0f, 1.0f,-1.0f,
+
+ -1.0f, 1.0f, 1.0f, // +Z side
+ -1.0f,-1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f,
+ -1.0f,-1.0f, 1.0f,
+ 1.0f,-1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f,
+};
+
+static const float g_uv_buffer_data[] = {
+ 0.0f, 1.0f, // -X side
+ 1.0f, 1.0f,
+ 1.0f, 0.0f,
+ 1.0f, 0.0f,
+ 0.0f, 0.0f,
+ 0.0f, 1.0f,
+
+ 1.0f, 1.0f, // -Z side
+ 0.0f, 0.0f,
+ 0.0f, 1.0f,
+ 1.0f, 1.0f,
+ 1.0f, 0.0f,
+ 0.0f, 0.0f,
+
+ 1.0f, 0.0f, // -Y side
+ 1.0f, 1.0f,
+ 0.0f, 1.0f,
+ 1.0f, 0.0f,
+ 0.0f, 1.0f,
+ 0.0f, 0.0f,
+
+ 1.0f, 0.0f, // +Y side
+ 0.0f, 0.0f,
+ 0.0f, 1.0f,
+ 1.0f, 0.0f,
+ 0.0f, 1.0f,
+ 1.0f, 1.0f,
+
+ 1.0f, 0.0f, // +X side
+ 0.0f, 0.0f,
+ 0.0f, 1.0f,
+ 0.0f, 1.0f,
+ 1.0f, 1.0f,
+ 1.0f, 0.0f,
+
+ 0.0f, 0.0f, // +Z side
+ 0.0f, 1.0f,
+ 1.0f, 0.0f,
+ 0.0f, 1.0f,
+ 1.0f, 1.0f,
+ 1.0f, 0.0f,
+};
+// clang-format on
+
+void dumpMatrix(const char *note, mat4x4 MVP) {
+ int i;
+
+ printf("%s: \n", note);
+ for (i = 0; i < 4; i++) {
+ printf("%f, %f, %f, %f\n", MVP[i][0], MVP[i][1], MVP[i][2], MVP[i][3]);
+ }
+ printf("\n");
+ fflush(stdout);
+}
+
+void dumpVec4(const char *note, vec4 vector) {
+ printf("%s: \n", note);
+ printf("%f, %f, %f, %f\n", vector[0], vector[1], vector[2], vector[3]);
+ printf("\n");
+ fflush(stdout);
+}
+
+VKAPI_ATTR VkBool32 VKAPI_CALL BreakCallback(VkFlags msgFlags, VkDebugReportObjectTypeEXT objType, uint64_t srcObject,
+ size_t location, int32_t msgCode, const char *pLayerPrefix, const char *pMsg,
+ void *pUserData) {
+#ifndef WIN32
+ raise(SIGTRAP);
+#else
+ DebugBreak();
+#endif
+
+ return false;
+}
+
+typedef struct {
+ VkImage image;
+ VkCommandBuffer cmd;
+ VkCommandBuffer graphics_to_present_cmd;
+ VkImageView view;
+ VkBuffer uniform_buffer;
+ VkDeviceMemory uniform_memory;
+ VkFramebuffer framebuffer;
+ VkDescriptorSet descriptor_set;
+} SwapchainImageResources;
+
+struct demo {
+#if defined(VK_USE_PLATFORM_WIN32_KHR)
+#define APP_NAME_STR_LEN 80
+ HINSTANCE connection; // hInstance - Windows Instance
+ char name[APP_NAME_STR_LEN]; // Name to put on the window/icon
+ HWND window; // hWnd - window handle
+ POINT minsize; // minimum window size
+#elif defined(VK_USE_PLATFORM_XLIB_KHR)
+ Display *display;
+ Window xlib_window;
+ Atom xlib_wm_delete_window;
+#elif defined(VK_USE_PLATFORM_XCB_KHR)
+ Display *display;
+ xcb_connection_t *connection;
+ xcb_screen_t *screen;
+ xcb_window_t xcb_window;
+ xcb_intern_atom_reply_t *atom_wm_delete_window;
+#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
+ struct wl_display *display;
+ struct wl_registry *registry;
+ struct wl_compositor *compositor;
+ struct wl_surface *window;
+ struct wl_shell *shell;
+ struct wl_shell_surface *shell_surface;
+#elif defined(VK_USE_PLATFORM_MIR_KHR)
+#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
+ ANativeWindow *window;
+#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK))
+ void *window;
+#endif
+ VkSurfaceKHR surface;
+ bool prepared;
+ bool use_staging_buffer;
+ bool separate_present_queue;
+
+ bool VK_KHR_incremental_present_enabled;
+
+ bool VK_GOOGLE_display_timing_enabled;
+ bool syncd_with_actual_presents;
+ uint64_t refresh_duration;
+ uint64_t refresh_duration_multiplier;
+ uint64_t target_IPD; // image present duration (inverse of frame rate)
+ uint64_t prev_desired_present_time;
+ uint32_t next_present_id;
+ uint32_t last_early_id; // 0 if no early images
+ uint32_t last_late_id; // 0 if no late images
+
+ VkInstance inst;
+ VkPhysicalDevice gpu;
+ VkDevice device;
+ VkQueue graphics_queue;
+ VkQueue present_queue;
+ uint32_t graphics_queue_family_index;
+ uint32_t present_queue_family_index;
+ VkSemaphore image_acquired_semaphores[FRAME_LAG];
+ VkSemaphore draw_complete_semaphores[FRAME_LAG];
+ VkSemaphore image_ownership_semaphores[FRAME_LAG];
+ VkPhysicalDeviceProperties gpu_props;
+ VkQueueFamilyProperties *queue_props;
+ VkPhysicalDeviceMemoryProperties memory_properties;
+
+ uint32_t enabled_extension_count;
+ uint32_t enabled_layer_count;
+ char *extension_names[64];
+ char *enabled_layers[64];
+
+ int width, height;
+ VkFormat format;
+ VkColorSpaceKHR color_space;
+
+ PFN_vkGetPhysicalDeviceSurfaceSupportKHR fpGetPhysicalDeviceSurfaceSupportKHR;
+ PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR fpGetPhysicalDeviceSurfaceCapabilitiesKHR;
+ PFN_vkGetPhysicalDeviceSurfaceFormatsKHR fpGetPhysicalDeviceSurfaceFormatsKHR;
+ PFN_vkGetPhysicalDeviceSurfacePresentModesKHR fpGetPhysicalDeviceSurfacePresentModesKHR;
+ PFN_vkCreateSwapchainKHR fpCreateSwapchainKHR;
+ PFN_vkDestroySwapchainKHR fpDestroySwapchainKHR;
+ PFN_vkGetSwapchainImagesKHR fpGetSwapchainImagesKHR;
+ PFN_vkAcquireNextImageKHR fpAcquireNextImageKHR;
+ PFN_vkQueuePresentKHR fpQueuePresentKHR;
+ PFN_vkGetRefreshCycleDurationGOOGLE fpGetRefreshCycleDurationGOOGLE;
+ PFN_vkGetPastPresentationTimingGOOGLE fpGetPastPresentationTimingGOOGLE;
+ uint32_t swapchainImageCount;
+ VkSwapchainKHR swapchain;
+ SwapchainImageResources *swapchain_image_resources;
+ VkPresentModeKHR presentMode;
+ VkFence fences[FRAME_LAG];
+ int frame_index;
+
+ VkCommandPool cmd_pool;
+ VkCommandPool present_cmd_pool;
+
+ struct {
+ VkFormat format;
+
+ VkImage image;
+ VkMemoryAllocateInfo mem_alloc;
+ VkDeviceMemory mem;
+ VkImageView view;
+ } depth;
+
+ struct texture_object textures[DEMO_TEXTURE_COUNT];
+ struct texture_object staging_texture;
+
+ VkCommandBuffer cmd; // Buffer for initialization commands
+ VkPipelineLayout pipeline_layout;
+ VkDescriptorSetLayout desc_layout;
+ VkPipelineCache pipelineCache;
+ VkRenderPass render_pass;
+ VkPipeline pipeline;
+
+ mat4x4 projection_matrix;
+ mat4x4 view_matrix;
+ mat4x4 model_matrix;
+
+ float spin_angle;
+ float spin_increment;
+ bool pause;
+
+ VkShaderModule vert_shader_module;
+ VkShaderModule frag_shader_module;
+
+ VkDescriptorPool desc_pool;
+
+ bool quit;
+ int32_t curFrame;
+ int32_t frameCount;
+ bool validate;
+ bool validate_checks_disabled;
+ bool use_break;
+ bool suppress_popups;
+ PFN_vkCreateDebugReportCallbackEXT CreateDebugReportCallback;
+ PFN_vkDestroyDebugReportCallbackEXT DestroyDebugReportCallback;
+ VkDebugReportCallbackEXT msg_callback;
+ PFN_vkDebugReportMessageEXT DebugReportMessage;
+
+ uint32_t current_buffer;
+ uint32_t queue_family_count;
+};
+
+VKAPI_ATTR VkBool32 VKAPI_CALL dbgFunc(VkFlags msgFlags, VkDebugReportObjectTypeEXT objType, uint64_t srcObject, size_t location,
+ int32_t msgCode, const char *pLayerPrefix, const char *pMsg, void *pUserData) {
+ // clang-format off
+ char *message = (char *)malloc(strlen(pMsg) + 100);
+
+ assert(message);
+
+ if (msgFlags & VK_DEBUG_REPORT_INFORMATION_BIT_EXT) {
+ sprintf(message, "INFORMATION: [%s] Code %d : %s", pLayerPrefix, msgCode, pMsg);
+ validation_error = 1;
+ } else if (msgFlags & VK_DEBUG_REPORT_WARNING_BIT_EXT) {
+ sprintf(message, "WARNING: [%s] Code %d : %s", pLayerPrefix, msgCode, pMsg);
+ validation_error = 1;
+ } else if (msgFlags & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT) {
+ sprintf(message, "PERFORMANCE WARNING: [%s] Code %d : %s", pLayerPrefix, msgCode, pMsg);
+ validation_error = 1;
+ } else if (msgFlags & VK_DEBUG_REPORT_ERROR_BIT_EXT) {
+ sprintf(message, "ERROR: [%s] Code %d : %s", pLayerPrefix, msgCode, pMsg);
+ validation_error = 1;
+ } else if (msgFlags & VK_DEBUG_REPORT_DEBUG_BIT_EXT) {
+ sprintf(message, "DEBUG: [%s] Code %d : %s", pLayerPrefix, msgCode, pMsg);
+ validation_error = 1;
+ } else {
+ sprintf(message, "INFORMATION: [%s] Code %d : %s", pLayerPrefix, msgCode, pMsg);
+ validation_error = 1;
+ }
+
+#ifdef _WIN32
+
+ in_callback = true;
+ struct demo *demo = (struct demo*) pUserData;
+ if (!demo->suppress_popups)
+ MessageBox(NULL, message, "Alert", MB_OK);
+ in_callback = false;
+
+#elif defined(ANDROID)
+
+ if (msgFlags & VK_DEBUG_REPORT_INFORMATION_BIT_EXT) {
+ __android_log_print(ANDROID_LOG_INFO, APP_SHORT_NAME, "%s", message);
+ } else if (msgFlags & VK_DEBUG_REPORT_WARNING_BIT_EXT) {
+ __android_log_print(ANDROID_LOG_WARN, APP_SHORT_NAME, "%s", message);
+ } else if (msgFlags & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT) {
+ __android_log_print(ANDROID_LOG_WARN, APP_SHORT_NAME, "%s", message);
+ } else if (msgFlags & VK_DEBUG_REPORT_ERROR_BIT_EXT) {
+ __android_log_print(ANDROID_LOG_ERROR, APP_SHORT_NAME, "%s", message);
+ } else if (msgFlags & VK_DEBUG_REPORT_DEBUG_BIT_EXT) {
+ __android_log_print(ANDROID_LOG_DEBUG, APP_SHORT_NAME, "%s", message);
+ } else {
+ __android_log_print(ANDROID_LOG_INFO, APP_SHORT_NAME, "%s", message);
+ }
+
+#else
+
+ printf("%s\n", message);
+ fflush(stdout);
+
+#endif
+
+ free(message);
+
+ //clang-format on
+
+ /*
+ * false indicates that layer should not bail-out of an
+ * API call that had validation failures. This may mean that the
+ * app dies inside the driver due to invalid parameter(s).
+ * That's what would happen without validation layers, so we'll
+ * keep that behavior here.
+ */
+ return false;
+}
+
+bool ActualTimeLate(uint64_t desired, uint64_t actual, uint64_t rdur) {
+ // The desired time was the earliest time that the present should have
+ // occured. In almost every case, the actual time should be later than the
+ // desired time. We should only consider the actual time "late" if it is
+ // after "desired + rdur".
+ if (actual <= desired) {
+ // The actual time was before or equal to the desired time. This will
+ // probably never happen, but in case it does, return false since the
+ // present was obviously NOT late.
+ return false;
+ }
+ uint64_t deadline = actual + rdur;
+ if (actual > deadline) {
+ return true;
+ } else {
+ return false;
+ }
+}
+bool CanPresentEarlier(uint64_t earliest,
+ uint64_t actual,
+ uint64_t margin,
+ uint64_t rdur) {
+ if (earliest < actual) {
+ // Consider whether this present could have occured earlier. Make sure
+ // that earliest time was at least 2msec earlier than actual time, and
+ // that the margin was at least 2msec:
+ uint64_t diff = actual - earliest;
+ if ((diff >= (2 * MILLION)) && (margin >= (2 * MILLION))) {
+ // This present could have occured earlier because both: 1) the
+ // earliest time was at least 2 msec before actual time, and 2) the
+ // margin was at least 2msec.
+ return true;
+ }
+ }
+ return false;
+}
+
+// Forward declaration:
+static void demo_resize(struct demo *demo);
+
+static bool memory_type_from_properties(struct demo *demo, uint32_t typeBits,
+ VkFlags requirements_mask,
+ uint32_t *typeIndex) {
+ // Search memtypes to find first index with those properties
+ for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; i++) {
+ if ((typeBits & 1) == 1) {
+ // Type is available, does it match user properties?
+ if ((demo->memory_properties.memoryTypes[i].propertyFlags &
+ requirements_mask) == requirements_mask) {
+ *typeIndex = i;
+ return true;
+ }
+ }
+ typeBits >>= 1;
+ }
+ // No memory types matched, return failure
+ return false;
+}
+
+static void demo_flush_init_cmd(struct demo *demo) {
+ VkResult U_ASSERT_ONLY err;
+
+ // This function could get called twice if the texture uses a staging buffer
+ // In that case the second call should be ignored
+ if (demo->cmd == VK_NULL_HANDLE)
+ return;
+
+ err = vkEndCommandBuffer(demo->cmd);
+ assert(!err);
+
+ VkFence fence;
+ VkFenceCreateInfo fence_ci = {.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
+ .pNext = NULL,
+ .flags = 0};
+ err = vkCreateFence(demo->device, &fence_ci, NULL, &fence);
+ assert(!err);
+
+ const VkCommandBuffer cmd_bufs[] = {demo->cmd};
+ VkSubmitInfo submit_info = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .pNext = NULL,
+ .waitSemaphoreCount = 0,
+ .pWaitSemaphores = NULL,
+ .pWaitDstStageMask = NULL,
+ .commandBufferCount = 1,
+ .pCommandBuffers = cmd_bufs,
+ .signalSemaphoreCount = 0,
+ .pSignalSemaphores = NULL};
+
+ err = vkQueueSubmit(demo->graphics_queue, 1, &submit_info, fence);
+ assert(!err);
+
+ err = vkWaitForFences(demo->device, 1, &fence, VK_TRUE, UINT64_MAX);
+ assert(!err);
+
+ vkFreeCommandBuffers(demo->device, demo->cmd_pool, 1, cmd_bufs);
+ vkDestroyFence(demo->device, fence, NULL);
+ demo->cmd = VK_NULL_HANDLE;
+}
+
+static void demo_set_image_layout(struct demo *demo, VkImage image,
+ VkImageAspectFlags aspectMask,
+ VkImageLayout old_image_layout,
+ VkImageLayout new_image_layout,
+ VkAccessFlagBits srcAccessMask,
+ VkPipelineStageFlags src_stages,
+ VkPipelineStageFlags dest_stages) {
+ assert(demo->cmd);
+
+ VkImageMemoryBarrier image_memory_barrier = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = NULL,
+ .srcAccessMask = srcAccessMask,
+ .dstAccessMask = 0,
+ .oldLayout = old_image_layout,
+ .newLayout = new_image_layout,
+ .image = image,
+ .subresourceRange = {aspectMask, 0, 1, 0, 1}};
+
+ switch (new_image_layout) {
+ case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
+ /* Make sure anything that was copying from this image has completed */
+ image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+ break;
+
+ case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
+ image_memory_barrier.dstAccessMask =
+ VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ break;
+
+ case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
+ image_memory_barrier.dstAccessMask =
+ VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+ break;
+
+ case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
+ image_memory_barrier.dstAccessMask =
+ VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
+ break;
+
+ case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
+ image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+ break;
+
+ case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
+ image_memory_barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+ break;
+
+ default:
+ image_memory_barrier.dstAccessMask = 0;
+ break;
+ }
+
+
+ VkImageMemoryBarrier *pmemory_barrier = &image_memory_barrier;
+
+ vkCmdPipelineBarrier(demo->cmd, src_stages, dest_stages, 0, 0, NULL, 0,
+ NULL, 1, pmemory_barrier);
+}
+
+static void demo_draw_build_cmd(struct demo *demo, VkCommandBuffer cmd_buf) {
+ const VkCommandBufferBeginInfo cmd_buf_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .pNext = NULL,
+ .flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
+ .pInheritanceInfo = NULL,
+ };
+ const VkClearValue clear_values[2] = {
+ [0] = {.color.float32 = {0.2f, 0.2f, 0.2f, 0.2f}},
+ [1] = {.depthStencil = {1.0f, 0}},
+ };
+ const VkRenderPassBeginInfo rp_begin = {
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
+ .pNext = NULL,
+ .renderPass = demo->render_pass,
+ .framebuffer = demo->swapchain_image_resources[demo->current_buffer].framebuffer,
+ .renderArea.offset.x = 0,
+ .renderArea.offset.y = 0,
+ .renderArea.extent.width = demo->width,
+ .renderArea.extent.height = demo->height,
+ .clearValueCount = 2,
+ .pClearValues = clear_values,
+ };
+ VkResult U_ASSERT_ONLY err;
+
+ err = vkBeginCommandBuffer(cmd_buf, &cmd_buf_info);
+ assert(!err);
+ vkCmdBeginRenderPass(cmd_buf, &rp_begin, VK_SUBPASS_CONTENTS_INLINE);
+ vkCmdBindPipeline(cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, demo->pipeline);
+ vkCmdBindDescriptorSets(cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS,
+ demo->pipeline_layout, 0, 1,
+ &demo->swapchain_image_resources[demo->current_buffer].descriptor_set,
+ 0, NULL);
+ VkViewport viewport;
+ memset(&viewport, 0, sizeof(viewport));
+ viewport.height = (float)demo->height;
+ viewport.width = (float)demo->width;
+ viewport.minDepth = (float)0.0f;
+ viewport.maxDepth = (float)1.0f;
+ vkCmdSetViewport(cmd_buf, 0, 1, &viewport);
+
+ VkRect2D scissor;
+ memset(&scissor, 0, sizeof(scissor));
+ scissor.extent.width = demo->width;
+ scissor.extent.height = demo->height;
+ scissor.offset.x = 0;
+ scissor.offset.y = 0;
+ vkCmdSetScissor(cmd_buf, 0, 1, &scissor);
+ vkCmdDraw(cmd_buf, 12 * 3, 1, 0, 0);
+ // Note that ending the renderpass changes the image's layout from
+ // COLOR_ATTACHMENT_OPTIMAL to PRESENT_SRC_KHR
+ vkCmdEndRenderPass(cmd_buf);
+
+ if (demo->separate_present_queue) {
+ // We have to transfer ownership from the graphics queue family to the
+ // present queue family to be able to present. Note that we don't have
+ // to transfer from present queue family back to graphics queue family at
+ // the start of the next frame because we don't care about the image's
+ // contents at that point.
+ VkImageMemoryBarrier image_ownership_barrier = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = NULL,
+ .srcAccessMask = 0,
+ .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+ .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+ .srcQueueFamilyIndex = demo->graphics_queue_family_index,
+ .dstQueueFamilyIndex = demo->present_queue_family_index,
+ .image = demo->swapchain_image_resources[demo->current_buffer].image,
+ .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
+
+ vkCmdPipelineBarrier(cmd_buf,
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0,
+ 0, NULL, 0, NULL, 1, &image_ownership_barrier);
+ }
+ err = vkEndCommandBuffer(cmd_buf);
+ assert(!err);
+}
+
+void demo_build_image_ownership_cmd(struct demo *demo, int i) {
+ VkResult U_ASSERT_ONLY err;
+
+ const VkCommandBufferBeginInfo cmd_buf_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .pNext = NULL,
+ .flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
+ .pInheritanceInfo = NULL,
+ };
+ err = vkBeginCommandBuffer(demo->swapchain_image_resources[i].graphics_to_present_cmd,
+ &cmd_buf_info);
+ assert(!err);
+
+ VkImageMemoryBarrier image_ownership_barrier = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = NULL,
+ .srcAccessMask = 0,
+ .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+ .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+ .srcQueueFamilyIndex = demo->graphics_queue_family_index,
+ .dstQueueFamilyIndex = demo->present_queue_family_index,
+ .image = demo->swapchain_image_resources[i].image,
+ .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
+
+ vkCmdPipelineBarrier(demo->swapchain_image_resources[i].graphics_to_present_cmd,
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0,
+ NULL, 0, NULL, 1, &image_ownership_barrier);
+ err = vkEndCommandBuffer(demo->swapchain_image_resources[i].graphics_to_present_cmd);
+ assert(!err);
+}
+
+void demo_update_data_buffer(struct demo *demo) {
+ mat4x4 MVP, Model, VP;
+ int matrixSize = sizeof(MVP);
+ uint8_t *pData;
+ VkResult U_ASSERT_ONLY err;
+
+ mat4x4_mul(VP, demo->projection_matrix, demo->view_matrix);
+
+ // Rotate around the Y axis
+ mat4x4_dup(Model, demo->model_matrix);
+ mat4x4_rotate(demo->model_matrix, Model, 0.0f, 1.0f, 0.0f,
+ (float)degreesToRadians(demo->spin_angle));
+ mat4x4_mul(MVP, VP, demo->model_matrix);
+
+ err = vkMapMemory(demo->device,
+ demo->swapchain_image_resources[demo->current_buffer].uniform_memory, 0,
+ VK_WHOLE_SIZE, 0, (void **)&pData);
+ assert(!err);
+
+ memcpy(pData, (const void *)&MVP[0][0], matrixSize);
+
+ vkUnmapMemory(demo->device, demo->swapchain_image_resources[demo->current_buffer].uniform_memory);
+}
+
+void DemoUpdateTargetIPD(struct demo *demo) {
+ // Look at what happened to previous presents, and make appropriate
+ // adjustments in timing:
+ VkResult U_ASSERT_ONLY err;
+ VkPastPresentationTimingGOOGLE* past = NULL;
+ uint32_t count = 0;
+
+ err = demo->fpGetPastPresentationTimingGOOGLE(demo->device,
+ demo->swapchain,
+ &count,
+ NULL);
+ assert(!err);
+ if (count) {
+ past = (VkPastPresentationTimingGOOGLE*) malloc(sizeof(VkPastPresentationTimingGOOGLE) * count);
+ assert(past);
+ err = demo->fpGetPastPresentationTimingGOOGLE(demo->device,
+ demo->swapchain,
+ &count,
+ past);
+ assert(!err);
+
+ bool early = false;
+ bool late = false;
+ bool calibrate_next = false;
+ for (uint32_t i = 0 ; i < count ; i++) {
+ if (!demo->syncd_with_actual_presents) {
+ // This is the first time that we've received an
+ // actualPresentTime for this swapchain. In order to not
+ // perceive these early frames as "late", we need to sync-up
+ // our future desiredPresentTime's with the
+ // actualPresentTime(s) that we're receiving now.
+ calibrate_next = true;
+
+ // So that we don't suspect any pending presents as late,
+ // record them all as suspected-late presents:
+ demo->last_late_id = demo->next_present_id - 1;
+ demo->last_early_id = 0;
+ demo->syncd_with_actual_presents = true;
+ break;
+ } else if (CanPresentEarlier(past[i].earliestPresentTime,
+ past[i].actualPresentTime,
+ past[i].presentMargin,
+ demo->refresh_duration)) {
+ // This image could have been presented earlier. We don't want
+ // to decrease the target_IPD until we've seen early presents
+ // for at least two seconds.
+ if (demo->last_early_id == past[i].presentID) {
+ // We've now seen two seconds worth of early presents.
+ // Flag it as such, and reset the counter:
+ early = true;
+ demo->last_early_id = 0;
+ } else if (demo->last_early_id == 0) {
+ // This is the first early present we've seen.
+ // Calculate the presentID for two seconds from now.
+ uint64_t lastEarlyTime =
+ past[i].actualPresentTime + (2 * BILLION);
+ uint32_t howManyPresents =
+ (uint32_t)((lastEarlyTime - past[i].actualPresentTime) / demo->target_IPD);
+ demo->last_early_id = past[i].presentID + howManyPresents;
+ } else {
+ // We are in the midst of a set of early images,
+ // and so we won't do anything.
+ }
+ late = false;
+ demo->last_late_id = 0;
+ } else if (ActualTimeLate(past[i].desiredPresentTime,
+ past[i].actualPresentTime,
+ demo->refresh_duration)) {
+ // This image was presented after its desired time. Since
+ // there's a delay between calling vkQueuePresentKHR and when
+ // we get the timing data, several presents may have been late.
+ // Thus, we need to threat all of the outstanding presents as
+ // being likely late, so that we only increase the target_IPD
+ // once for all of those presents.
+ if ((demo->last_late_id == 0) ||
+ (demo->last_late_id < past[i].presentID)) {
+ late = true;
+ // Record the last suspected-late present:
+ demo->last_late_id = demo->next_present_id - 1;
+ } else {
+ // We are in the midst of a set of likely-late images,
+ // and so we won't do anything.
+ }
+ early = false;
+ demo->last_early_id = 0;
+ } else {
+ // Since this image was not presented early or late, reset
+ // any sets of early or late presentIDs:
+ early = false;
+ late = false;
+ calibrate_next = true;
+ demo->last_early_id = 0;
+ demo->last_late_id = 0;
+ }
+ }
+
+ if (early) {
+ // Since we've seen at least two-seconds worth of presnts that
+ // could have occured earlier than desired, let's decrease the
+ // target_IPD (i.e. increase the frame rate):
+ //
+ // TODO(ianelliott): Try to calculate a better target_IPD based
+ // on the most recently-seen present (this is overly-simplistic).
+ demo->refresh_duration_multiplier--;
+ if (demo->refresh_duration_multiplier == 0) {
+ // This should never happen, but in case it does, don't
+ // try to go faster.
+ demo->refresh_duration_multiplier = 1;
+ }
+ demo->target_IPD =
+ demo->refresh_duration * demo->refresh_duration_multiplier;
+ }
+ if (late) {
+ // Since we found a new instance of a late present, we want to
+ // increase the target_IPD (i.e. decrease the frame rate):
+ //
+ // TODO(ianelliott): Try to calculate a better target_IPD based
+ // on the most recently-seen present (this is overly-simplistic).
+ demo->refresh_duration_multiplier++;
+ demo->target_IPD =
+ demo->refresh_duration * demo->refresh_duration_multiplier;
+ }
+
+ if (calibrate_next) {
+ int64_t multiple = demo->next_present_id - past[count-1].presentID;
+ demo->prev_desired_present_time =
+ (past[count-1].actualPresentTime +
+ (multiple * demo->target_IPD));
+ }
+ }
+}
+
+static void demo_draw(struct demo *demo) {
+ VkResult U_ASSERT_ONLY err;
+
+ // Ensure no more than FRAME_LAG renderings are outstanding
+ vkWaitForFences(demo->device, 1, &demo->fences[demo->frame_index], VK_TRUE, UINT64_MAX);
+ vkResetFences(demo->device, 1, &demo->fences[demo->frame_index]);
+
+ // Get the index of the next available swapchain image:
+ err = demo->fpAcquireNextImageKHR(demo->device, demo->swapchain, UINT64_MAX,
+ demo->image_acquired_semaphores[demo->frame_index],
+ VK_NULL_HANDLE, &demo->current_buffer);
+
+ demo_update_data_buffer(demo);
+
+ if (err == VK_ERROR_OUT_OF_DATE_KHR) {
+ // demo->swapchain is out of date (e.g. the window was resized) and
+ // must be recreated:
+ demo->frame_index += 1;
+ demo->frame_index %= FRAME_LAG;
+
+ demo_resize(demo);
+ demo_draw(demo);
+ return;
+ } else if (err == VK_SUBOPTIMAL_KHR) {
+ // demo->swapchain is not as optimal as it could be, but the platform's
+ // presentation engine will still present the image correctly.
+ } else {
+ assert(!err);
+ }
+ if (demo->VK_GOOGLE_display_timing_enabled) {
+ // Look at what happened to previous presents, and make appropriate
+ // adjustments in timing:
+ DemoUpdateTargetIPD(demo);
+
+ // Note: a real application would position its geometry to that it's in
+ // the correct locatoin for when the next image is presented. It might
+ // also wait, so that there's less latency between any input and when
+ // the next image is rendered/presented. This demo program is so
+ // simple that it doesn't do either of those.
+ }
+
+ // Wait for the image acquired semaphore to be signaled to ensure
+ // that the image won't be rendered to until the presentation
+ // engine has fully released ownership to the application, and it is
+ // okay to render to the image.
+ VkPipelineStageFlags pipe_stage_flags;
+ VkSubmitInfo submit_info;
+ submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+ submit_info.pNext = NULL;
+ submit_info.pWaitDstStageMask = &pipe_stage_flags;
+ pipe_stage_flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ submit_info.waitSemaphoreCount = 1;
+ submit_info.pWaitSemaphores = &demo->image_acquired_semaphores[demo->frame_index];
+ submit_info.commandBufferCount = 1;
+ submit_info.pCommandBuffers = &demo->swapchain_image_resources[demo->current_buffer].cmd;
+ submit_info.signalSemaphoreCount = 1;
+ submit_info.pSignalSemaphores = &demo->draw_complete_semaphores[demo->frame_index];
+ err = vkQueueSubmit(demo->graphics_queue, 1, &submit_info,
+ demo->fences[demo->frame_index]);
+ assert(!err);
+
+ if (demo->separate_present_queue) {
+ // If we are using separate queues, change image ownership to the
+ // present queue before presenting, waiting for the draw complete
+ // semaphore and signalling the ownership released semaphore when finished
+ VkFence nullFence = VK_NULL_HANDLE;
+ pipe_stage_flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ submit_info.waitSemaphoreCount = 1;
+ submit_info.pWaitSemaphores = &demo->draw_complete_semaphores[demo->frame_index];
+ submit_info.commandBufferCount = 1;
+ submit_info.pCommandBuffers =
+ &demo->swapchain_image_resources[demo->current_buffer].graphics_to_present_cmd;
+ submit_info.signalSemaphoreCount = 1;
+ submit_info.pSignalSemaphores = &demo->image_ownership_semaphores[demo->frame_index];
+ err = vkQueueSubmit(demo->present_queue, 1, &submit_info, nullFence);
+ assert(!err);
+ }
+
+ // If we are using separate queues we have to wait for image ownership,
+ // otherwise wait for draw complete
+ VkPresentInfoKHR present = {
+ .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+ .pNext = NULL,
+ .waitSemaphoreCount = 1,
+ .pWaitSemaphores = (demo->separate_present_queue)
+ ? &demo->image_ownership_semaphores[demo->frame_index]
+ : &demo->draw_complete_semaphores[demo->frame_index],
+ .swapchainCount = 1,
+ .pSwapchains = &demo->swapchain,
+ .pImageIndices = &demo->current_buffer,
+ };
+
+ if (demo->VK_KHR_incremental_present_enabled) {
+ // If using VK_KHR_incremental_present, we provide a hint of the region
+ // that contains changed content relative to the previously-presented
+ // image. The implementation can use this hint in order to save
+ // work/power (by only copying the region in the hint). The
+ // implementation is free to ignore the hint though, and so we must
+ // ensure that the entire image has the correctly-drawn content.
+ uint32_t eighthOfWidth = demo->width / 8;
+ uint32_t eighthOfHeight = demo->height / 8;
+ VkRectLayerKHR rect = {
+ .offset.x = eighthOfWidth,
+ .offset.y = eighthOfHeight,
+ .extent.width = eighthOfWidth * 6,
+ .extent.height = eighthOfHeight * 6,
+ .layer = 0,
+ };
+ VkPresentRegionKHR region = {
+ .rectangleCount = 1,
+ .pRectangles = &rect,
+ };
+ VkPresentRegionsKHR regions = {
+ .sType = VK_STRUCTURE_TYPE_PRESENT_REGIONS_KHR,
+ .pNext = present.pNext,
+ .swapchainCount = present.swapchainCount,
+ .pRegions = &region,
+ };
+ present.pNext = &regions;
+ }
+
+ if (demo->VK_GOOGLE_display_timing_enabled) {
+ VkPresentTimeGOOGLE ptime;
+ if (demo->prev_desired_present_time == 0) {
+ // This must be the first present for this swapchain.
+ //
+ // We don't know where we are relative to the presentation engine's
+ // display's refresh cycle. We also don't know how long rendering
+ // takes. Let's make a grossly-simplified assumption that the
+ // desiredPresentTime should be half way between now and
+ // now+target_IPD. We will adjust over time.
+ uint64_t curtime = getTimeInNanoseconds();
+ if (curtime == 0) {
+ // Since we didn't find out the current time, don't give a
+ // desiredPresentTime:
+ ptime.desiredPresentTime = 0;
+ } else {
+ ptime.desiredPresentTime = curtime + (demo->target_IPD >> 1);
+ }
+ } else {
+ ptime.desiredPresentTime = (demo->prev_desired_present_time +
+ demo->target_IPD);
+ }
+ ptime.presentID = demo->next_present_id++;
+ demo->prev_desired_present_time = ptime.desiredPresentTime;
+
+ VkPresentTimesInfoGOOGLE present_time = {
+ .sType = VK_STRUCTURE_TYPE_PRESENT_TIMES_INFO_GOOGLE,
+ .pNext = present.pNext,
+ .swapchainCount = present.swapchainCount,
+ .pTimes = &ptime,
+ };
+ if (demo->VK_GOOGLE_display_timing_enabled) {
+ present.pNext = &present_time;
+ }
+ }
+
+ err = demo->fpQueuePresentKHR(demo->present_queue, &present);
+ demo->frame_index += 1;
+ demo->frame_index %= FRAME_LAG;
+
+ if (err == VK_ERROR_OUT_OF_DATE_KHR) {
+ // demo->swapchain is out of date (e.g. the window was resized) and
+ // must be recreated:
+ demo_resize(demo);
+ } else if (err == VK_SUBOPTIMAL_KHR) {
+ // demo->swapchain is not as optimal as it could be, but the platform's
+ // presentation engine will still present the image correctly.
+ } else {
+ assert(!err);
+ }
+}
+
+static void demo_prepare_buffers(struct demo *demo) {
+ VkResult U_ASSERT_ONLY err;
+ VkSwapchainKHR oldSwapchain = demo->swapchain;
+
+ // Check the surface capabilities and formats
+ VkSurfaceCapabilitiesKHR surfCapabilities;
+ err = demo->fpGetPhysicalDeviceSurfaceCapabilitiesKHR(
+ demo->gpu, demo->surface, &surfCapabilities);
+ assert(!err);
+
+ uint32_t presentModeCount;
+ err = demo->fpGetPhysicalDeviceSurfacePresentModesKHR(
+ demo->gpu, demo->surface, &presentModeCount, NULL);
+ assert(!err);
+ VkPresentModeKHR *presentModes =
+ (VkPresentModeKHR *)malloc(presentModeCount * sizeof(VkPresentModeKHR));
+ assert(presentModes);
+ err = demo->fpGetPhysicalDeviceSurfacePresentModesKHR(
+ demo->gpu, demo->surface, &presentModeCount, presentModes);
+ assert(!err);
+
+ VkExtent2D swapchainExtent;
+ // width and height are either both 0xFFFFFFFF, or both not 0xFFFFFFFF.
+ if (surfCapabilities.currentExtent.width == 0xFFFFFFFF) {
+ // If the surface size is undefined, the size is set to the size
+ // of the images requested, which must fit within the minimum and
+ // maximum values.
+ swapchainExtent.width = demo->width;
+ swapchainExtent.height = demo->height;
+
+ if (swapchainExtent.width < surfCapabilities.minImageExtent.width) {
+ swapchainExtent.width = surfCapabilities.minImageExtent.width;
+ } else if (swapchainExtent.width > surfCapabilities.maxImageExtent.width) {
+ swapchainExtent.width = surfCapabilities.maxImageExtent.width;
+ }
+
+ if (swapchainExtent.height < surfCapabilities.minImageExtent.height) {
+ swapchainExtent.height = surfCapabilities.minImageExtent.height;
+ } else if (swapchainExtent.height > surfCapabilities.maxImageExtent.height) {
+ swapchainExtent.height = surfCapabilities.maxImageExtent.height;
+ }
+ } else {
+ // If the surface size is defined, the swap chain size must match
+ swapchainExtent = surfCapabilities.currentExtent;
+ demo->width = surfCapabilities.currentExtent.width;
+ demo->height = surfCapabilities.currentExtent.height;
+ }
+
+ // The FIFO present mode is guaranteed by the spec to be supported
+ // and to have no tearing. It's a great default present mode to use.
+ VkPresentModeKHR swapchainPresentMode = VK_PRESENT_MODE_FIFO_KHR;
+
+ // There are times when you may wish to use another present mode. The
+ // following code shows how to select them, and the comments provide some
+ // reasons you may wish to use them.
+ //
+ // It should be noted that Vulkan 1.0 doesn't provide a method for
+ // synchronizing rendering with the presentation engine's display. There
+ // is a method provided for throttling rendering with the display, but
+ // there are some presentation engines for which this method will not work.
+ // If an application doesn't throttle its rendering, and if it renders much
+ // faster than the refresh rate of the display, this can waste power on
+ // mobile devices. That is because power is being spent rendering images
+ // that may never be seen.
+
+ // VK_PRESENT_MODE_IMMEDIATE_KHR is for applications that don't care about
+ // tearing, or have some way of synchronizing their rendering with the
+ // display.
+ // VK_PRESENT_MODE_MAILBOX_KHR may be useful for applications that
+ // generally render a new presentable image every refresh cycle, but are
+ // occasionally early. In this case, the application wants the new image
+ // to be displayed instead of the previously-queued-for-presentation image
+ // that has not yet been displayed.
+ // VK_PRESENT_MODE_FIFO_RELAXED_KHR is for applications that generally
+ // render a new presentable image every refresh cycle, but are occasionally
+ // late. In this case (perhaps because of stuttering/latency concerns),
+ // the application wants the late image to be immediately displayed, even
+ // though that may mean some tearing.
+
+ if (demo->presentMode != swapchainPresentMode) {
+
+ for (size_t i = 0; i < presentModeCount; ++i) {
+ if (presentModes[i] == demo->presentMode) {
+ swapchainPresentMode = demo->presentMode;
+ break;
+ }
+ }
+ }
+ if (swapchainPresentMode != demo->presentMode) {
+ ERR_EXIT("Present mode specified is not supported\n", "Present mode unsupported");
+ }
+
+ // Determine the number of VkImages to use in the swap chain.
+ // Application desires to acquire 3 images at a time for triple
+ // buffering
+ uint32_t desiredNumOfSwapchainImages = 3;
+ if (desiredNumOfSwapchainImages < surfCapabilities.minImageCount) {
+ desiredNumOfSwapchainImages = surfCapabilities.minImageCount;
+ }
+ // If maxImageCount is 0, we can ask for as many images as we want;
+ // otherwise we're limited to maxImageCount
+ if ((surfCapabilities.maxImageCount > 0) &&
+ (desiredNumOfSwapchainImages > surfCapabilities.maxImageCount)) {
+ // Application must settle for fewer images than desired:
+ desiredNumOfSwapchainImages = surfCapabilities.maxImageCount;
+ }
+
+ VkSurfaceTransformFlagsKHR preTransform;
+ if (surfCapabilities.supportedTransforms &
+ VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) {
+ preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
+ } else {
+ preTransform = surfCapabilities.currentTransform;
+ }
+
+ // Find a supported composite alpha mode - one of these is guaranteed to be set
+ VkCompositeAlphaFlagBitsKHR compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
+ VkCompositeAlphaFlagBitsKHR compositeAlphaFlags[4] = {
+ VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
+ VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR,
+ VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR,
+ VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR,
+ };
+ for (uint32_t i = 0; i < sizeof(compositeAlphaFlags); i++) {
+ if (surfCapabilities.supportedCompositeAlpha & compositeAlphaFlags[i]) {
+ compositeAlpha = compositeAlphaFlags[i];
+ break;
+ }
+ }
+
+ VkSwapchainCreateInfoKHR swapchain_ci = {
+ .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
+ .pNext = NULL,
+ .surface = demo->surface,
+ .minImageCount = desiredNumOfSwapchainImages,
+ .imageFormat = demo->format,
+ .imageColorSpace = demo->color_space,
+ .imageExtent =
+ {
+ .width = swapchainExtent.width, .height = swapchainExtent.height,
+ },
+ .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ .preTransform = preTransform,
+ .compositeAlpha = compositeAlpha,
+ .imageArrayLayers = 1,
+ .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 0,
+ .pQueueFamilyIndices = NULL,
+ .presentMode = swapchainPresentMode,
+ .oldSwapchain = oldSwapchain,
+ .clipped = true,
+ };
+ uint32_t i;
+ err = demo->fpCreateSwapchainKHR(demo->device, &swapchain_ci, NULL,
+ &demo->swapchain);
+ assert(!err);
+
+ // If we just re-created an existing swapchain, we should destroy the old
+ // swapchain at this point.
+ // Note: destroying the swapchain also cleans up all its associated
+ // presentable images once the platform is done with them.
+ if (oldSwapchain != VK_NULL_HANDLE) {
+ // AMD driver times out waiting on fences used in AcquireNextImage on
+ // a swapchain that is subsequently destroyed before the wait.
+ vkWaitForFences(demo->device, FRAME_LAG, demo->fences, VK_TRUE, UINT64_MAX);
+ demo->fpDestroySwapchainKHR(demo->device, oldSwapchain, NULL);
+ }
+
+ err = demo->fpGetSwapchainImagesKHR(demo->device, demo->swapchain,
+ &demo->swapchainImageCount, NULL);
+ assert(!err);
+
+ VkImage *swapchainImages =
+ (VkImage *)malloc(demo->swapchainImageCount * sizeof(VkImage));
+ assert(swapchainImages);
+ err = demo->fpGetSwapchainImagesKHR(demo->device, demo->swapchain,
+ &demo->swapchainImageCount,
+ swapchainImages);
+ assert(!err);
+
+ demo->swapchain_image_resources = (SwapchainImageResources *)malloc(sizeof(SwapchainImageResources) *
+ demo->swapchainImageCount);
+ assert(demo->swapchain_image_resources);
+
+ for (i = 0; i < demo->swapchainImageCount; i++) {
+ VkImageViewCreateInfo color_image_view = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .pNext = NULL,
+ .format = demo->format,
+ .components =
+ {
+ .r = VK_COMPONENT_SWIZZLE_R,
+ .g = VK_COMPONENT_SWIZZLE_G,
+ .b = VK_COMPONENT_SWIZZLE_B,
+ .a = VK_COMPONENT_SWIZZLE_A,
+ },
+ .subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1},
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .flags = 0,
+ };
+
+ demo->swapchain_image_resources[i].image = swapchainImages[i];
+
+ color_image_view.image = demo->swapchain_image_resources[i].image;
+
+ err = vkCreateImageView(demo->device, &color_image_view, NULL,
+ &demo->swapchain_image_resources[i].view);
+ assert(!err);
+ }
+
+ if (demo->VK_GOOGLE_display_timing_enabled) {
+ VkRefreshCycleDurationGOOGLE rc_dur;
+ err = demo->fpGetRefreshCycleDurationGOOGLE(demo->device,
+ demo->swapchain,
+ &rc_dur);
+ assert(!err);
+ demo->refresh_duration = rc_dur.refreshDuration;
+
+ demo->syncd_with_actual_presents = false;
+ // Initially target 1X the refresh duration:
+ demo->target_IPD = demo->refresh_duration;
+ demo->refresh_duration_multiplier = 1;
+ demo->prev_desired_present_time = 0;
+ demo->next_present_id = 1;
+ }
+
+ if (NULL != presentModes) {
+ free(presentModes);
+ }
+}
+
+static void demo_prepare_depth(struct demo *demo) {
+ const VkFormat depth_format = VK_FORMAT_D16_UNORM;
+ const VkImageCreateInfo image = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .pNext = NULL,
+ .imageType = VK_IMAGE_TYPE_2D,
+ .format = depth_format,
+ .extent = {demo->width, demo->height, 1},
+ .mipLevels = 1,
+ .arrayLayers = 1,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .tiling = VK_IMAGE_TILING_OPTIMAL,
+ .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
+ .flags = 0,
+ };
+
+ VkImageViewCreateInfo view = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .pNext = NULL,
+ .image = VK_NULL_HANDLE,
+ .format = depth_format,
+ .subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1},
+ .flags = 0,
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ };
+
+ VkMemoryRequirements mem_reqs;
+ VkResult U_ASSERT_ONLY err;
+ bool U_ASSERT_ONLY pass;
+
+ demo->depth.format = depth_format;
+
+ /* create image */
+ err = vkCreateImage(demo->device, &image, NULL, &demo->depth.image);
+ assert(!err);
+
+ vkGetImageMemoryRequirements(demo->device, demo->depth.image, &mem_reqs);
+ assert(!err);
+
+ demo->depth.mem_alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+ demo->depth.mem_alloc.pNext = NULL;
+ demo->depth.mem_alloc.allocationSize = mem_reqs.size;
+ demo->depth.mem_alloc.memoryTypeIndex = 0;
+
+ pass = memory_type_from_properties(demo, mem_reqs.memoryTypeBits,
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+ &demo->depth.mem_alloc.memoryTypeIndex);
+ assert(pass);
+
+ /* allocate memory */
+ err = vkAllocateMemory(demo->device, &demo->depth.mem_alloc, NULL,
+ &demo->depth.mem);
+ assert(!err);
+
+ /* bind memory */
+ err =
+ vkBindImageMemory(demo->device, demo->depth.image, demo->depth.mem, 0);
+ assert(!err);
+
+ /* create image view */
+ view.image = demo->depth.image;
+ err = vkCreateImageView(demo->device, &view, NULL, &demo->depth.view);
+ assert(!err);
+}
+
+/* Load a ppm file into memory */
+bool loadTexture(const char *filename, uint8_t *rgba_data,
+ VkSubresourceLayout *layout, int32_t *width, int32_t *height) {
+
+#if (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK))
+ filename =[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: @(filename)].UTF8String;
+#endif
+
+#ifdef __ANDROID__
+#include <lunarg.ppm.h>
+ char *cPtr;
+ cPtr = (char*)lunarg_ppm;
+ if ((unsigned char*)cPtr >= (lunarg_ppm + lunarg_ppm_len) || strncmp(cPtr, "P6\n", 3)) {
+ return false;
+ }
+ while(strncmp(cPtr++, "\n", 1));
+ sscanf(cPtr, "%u %u", width, height);
+ if (rgba_data == NULL) {
+ return true;
+ }
+ while(strncmp(cPtr++, "\n", 1));
+ if ((unsigned char*)cPtr >= (lunarg_ppm + lunarg_ppm_len) || strncmp(cPtr, "255\n", 4)) {
+ return false;
+ }
+ while(strncmp(cPtr++, "\n", 1));
+
+ for (int y = 0; y < *height; y++) {
+ uint8_t *rowPtr = rgba_data;
+ for (int x = 0; x < *width; x++) {
+ memcpy(rowPtr, cPtr, 3);
+ rowPtr[3] = 255; /* Alpha of 1 */
+ rowPtr += 4;
+ cPtr += 3;
+ }
+ rgba_data += layout->rowPitch;
+ }
+
+ return true;
+#else
+ FILE *fPtr = fopen(filename, "rb");
+ char header[256], *cPtr, *tmp;
+
+ if (!fPtr)
+ return false;
+
+ cPtr = fgets(header, 256, fPtr); // P6
+ if (cPtr == NULL || strncmp(header, "P6\n", 3)) {
+ fclose(fPtr);
+ return false;
+ }
+
+ do {
+ cPtr = fgets(header, 256, fPtr);
+ if (cPtr == NULL) {
+ fclose(fPtr);
+ return false;
+ }
+ } while (!strncmp(header, "#", 1));
+
+ sscanf(header, "%u %u", width, height);
+ if (rgba_data == NULL) {
+ fclose(fPtr);
+ return true;
+ }
+ tmp = fgets(header, 256, fPtr); // Format
+ (void)tmp;
+ if (cPtr == NULL || strncmp(header, "255\n", 3)) {
+ fclose(fPtr);
+ return false;
+ }
+
+ for (int y = 0; y < *height; y++) {
+ uint8_t *rowPtr = rgba_data;
+ for (int x = 0; x < *width; x++) {
+ size_t s = fread(rowPtr, 3, 1, fPtr);
+ (void)s;
+ rowPtr[3] = 255; /* Alpha of 1 */
+ rowPtr += 4;
+ }
+ rgba_data += layout->rowPitch;
+ }
+ fclose(fPtr);
+ return true;
+#endif
+}
+
+static void demo_prepare_texture_image(struct demo *demo, const char *filename,
+ struct texture_object *tex_obj,
+ VkImageTiling tiling,
+ VkImageUsageFlags usage,
+ VkFlags required_props) {
+ const VkFormat tex_format = VK_FORMAT_R8G8B8A8_UNORM;
+ int32_t tex_width;
+ int32_t tex_height;
+ VkResult U_ASSERT_ONLY err;
+ bool U_ASSERT_ONLY pass;
+
+ if (!loadTexture(filename, NULL, NULL, &tex_width, &tex_height)) {
+ ERR_EXIT("Failed to load textures", "Load Texture Failure");
+ }
+
+ tex_obj->tex_width = tex_width;
+ tex_obj->tex_height = tex_height;
+
+ const VkImageCreateInfo image_create_info = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .pNext = NULL,
+ .imageType = VK_IMAGE_TYPE_2D,
+ .format = tex_format,
+ .extent = {tex_width, tex_height, 1},
+ .mipLevels = 1,
+ .arrayLayers = 1,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .tiling = tiling,
+ .usage = usage,
+ .flags = 0,
+ .initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED,
+ };
+
+ VkMemoryRequirements mem_reqs;
+
+ err =
+ vkCreateImage(demo->device, &image_create_info, NULL, &tex_obj->image);
+ assert(!err);
+
+ vkGetImageMemoryRequirements(demo->device, tex_obj->image, &mem_reqs);
+
+ tex_obj->mem_alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+ tex_obj->mem_alloc.pNext = NULL;
+ tex_obj->mem_alloc.allocationSize = mem_reqs.size;
+ tex_obj->mem_alloc.memoryTypeIndex = 0;
+
+ pass = memory_type_from_properties(demo, mem_reqs.memoryTypeBits,
+ required_props,
+ &tex_obj->mem_alloc.memoryTypeIndex);
+ assert(pass);
+
+ /* allocate memory */
+ err = vkAllocateMemory(demo->device, &tex_obj->mem_alloc, NULL,
+ &(tex_obj->mem));
+ assert(!err);
+
+ /* bind memory */
+ err = vkBindImageMemory(demo->device, tex_obj->image, tex_obj->mem, 0);
+ assert(!err);
+
+ if (required_props & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) {
+ const VkImageSubresource subres = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .mipLevel = 0,
+ .arrayLayer = 0,
+ };
+ VkSubresourceLayout layout;
+ void *data;
+
+ vkGetImageSubresourceLayout(demo->device, tex_obj->image, &subres,
+ &layout);
+
+ err = vkMapMemory(demo->device, tex_obj->mem, 0,
+ tex_obj->mem_alloc.allocationSize, 0, &data);
+ assert(!err);
+
+ if (!loadTexture(filename, data, &layout, &tex_width, &tex_height)) {
+ fprintf(stderr, "Error loading texture: %s\n", filename);
+ }
+
+ vkUnmapMemory(demo->device, tex_obj->mem);
+ }
+
+ tex_obj->imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+}
+
+static void demo_destroy_texture_image(struct demo *demo,
+ struct texture_object *tex_objs) {
+ /* clean up staging resources */
+ vkFreeMemory(demo->device, tex_objs->mem, NULL);
+ vkDestroyImage(demo->device, tex_objs->image, NULL);
+}
+
+static void demo_prepare_textures(struct demo *demo) {
+ const VkFormat tex_format = VK_FORMAT_R8G8B8A8_UNORM;
+ VkFormatProperties props;
+ uint32_t i;
+
+ vkGetPhysicalDeviceFormatProperties(demo->gpu, tex_format, &props);
+
+ for (i = 0; i < DEMO_TEXTURE_COUNT; i++) {
+ VkResult U_ASSERT_ONLY err;
+
+ if ((props.linearTilingFeatures &
+ VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) &&
+ !demo->use_staging_buffer) {
+ /* Device can texture using linear textures */
+ demo_prepare_texture_image(
+ demo, tex_files[i], &demo->textures[i], VK_IMAGE_TILING_LINEAR,
+ VK_IMAGE_USAGE_SAMPLED_BIT,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+ // Nothing in the pipeline needs to be complete to start, and don't allow fragment
+ // shader to run until layout transition completes
+ demo_set_image_layout(demo, demo->textures[i].image, VK_IMAGE_ASPECT_COLOR_BIT,
+ VK_IMAGE_LAYOUT_PREINITIALIZED, demo->textures[i].imageLayout,
+ VK_ACCESS_HOST_WRITE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
+ demo->staging_texture.image = 0;
+ } else if (props.optimalTilingFeatures &
+ VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) {
+ /* Must use staging buffer to copy linear texture to optimized */
+
+ memset(&demo->staging_texture, 0, sizeof(demo->staging_texture));
+ demo_prepare_texture_image(
+ demo, tex_files[i], &demo->staging_texture, VK_IMAGE_TILING_LINEAR,
+ VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+
+ demo_prepare_texture_image(
+ demo, tex_files[i], &demo->textures[i], VK_IMAGE_TILING_OPTIMAL,
+ (VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT),
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+
+ demo_set_image_layout(demo, demo->staging_texture.image,
+ VK_IMAGE_ASPECT_COLOR_BIT,
+ VK_IMAGE_LAYOUT_PREINITIALIZED,
+ VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ VK_ACCESS_HOST_WRITE_BIT,
+ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT);
+
+ demo_set_image_layout(demo, demo->textures[i].image,
+ VK_IMAGE_ASPECT_COLOR_BIT,
+ VK_IMAGE_LAYOUT_PREINITIALIZED,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ VK_ACCESS_HOST_WRITE_BIT,
+ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT);
+
+ VkImageCopy copy_region = {
+ .srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
+ .srcOffset = {0, 0, 0},
+ .dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
+ .dstOffset = {0, 0, 0},
+ .extent = {demo->staging_texture.tex_width,
+ demo->staging_texture.tex_height, 1},
+ };
+ vkCmdCopyImage(
+ demo->cmd, demo->staging_texture.image,
+ VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, demo->textures[i].image,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copy_region);
+
+ demo_set_image_layout(demo, demo->textures[i].image,
+ VK_IMAGE_ASPECT_COLOR_BIT,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ demo->textures[i].imageLayout,
+ VK_ACCESS_TRANSFER_WRITE_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
+
+ } else {
+ /* Can't support VK_FORMAT_R8G8B8A8_UNORM !? */
+ assert(!"No support for R8G8B8A8_UNORM as texture image format");
+ }
+
+ const VkSamplerCreateInfo sampler = {
+ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
+ .pNext = NULL,
+ .magFilter = VK_FILTER_NEAREST,
+ .minFilter = VK_FILTER_NEAREST,
+ .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST,
+ .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
+ .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
+ .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
+ .mipLodBias = 0.0f,
+ .anisotropyEnable = VK_FALSE,
+ .maxAnisotropy = 1,
+ .compareOp = VK_COMPARE_OP_NEVER,
+ .minLod = 0.0f,
+ .maxLod = 0.0f,
+ .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE,
+ .unnormalizedCoordinates = VK_FALSE,
+ };
+
+ VkImageViewCreateInfo view = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .pNext = NULL,
+ .image = VK_NULL_HANDLE,
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = tex_format,
+ .components =
+ {
+ VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G,
+ VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A,
+ },
+ .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1},
+ .flags = 0,
+ };
+
+ /* create sampler */
+ err = vkCreateSampler(demo->device, &sampler, NULL,
+ &demo->textures[i].sampler);
+ assert(!err);
+
+ /* create image view */
+ view.image = demo->textures[i].image;
+ err = vkCreateImageView(demo->device, &view, NULL,
+ &demo->textures[i].view);
+ assert(!err);
+ }
+}
+
+void demo_prepare_cube_data_buffers(struct demo *demo) {
+ VkBufferCreateInfo buf_info;
+ VkMemoryRequirements mem_reqs;
+ VkMemoryAllocateInfo mem_alloc;
+ uint8_t *pData;
+ mat4x4 MVP, VP;
+ VkResult U_ASSERT_ONLY err;
+ bool U_ASSERT_ONLY pass;
+ struct vktexcube_vs_uniform data;
+
+ mat4x4_mul(VP, demo->projection_matrix, demo->view_matrix);
+ mat4x4_mul(MVP, VP, demo->model_matrix);
+ memcpy(data.mvp, MVP, sizeof(MVP));
+ // dumpMatrix("MVP", MVP);
+
+ for (unsigned int i = 0; i < 12 * 3; i++) {
+ data.position[i][0] = g_vertex_buffer_data[i * 3];
+ data.position[i][1] = g_vertex_buffer_data[i * 3 + 1];
+ data.position[i][2] = g_vertex_buffer_data[i * 3 + 2];
+ data.position[i][3] = 1.0f;
+ data.attr[i][0] = g_uv_buffer_data[2 * i];
+ data.attr[i][1] = g_uv_buffer_data[2 * i + 1];
+ data.attr[i][2] = 0;
+ data.attr[i][3] = 0;
+ }
+
+ memset(&buf_info, 0, sizeof(buf_info));
+ buf_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+ buf_info.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
+ buf_info.size = sizeof(data);
+
+ for (unsigned int i = 0; i < demo->swapchainImageCount; i++) {
+ err =
+ vkCreateBuffer(demo->device, &buf_info, NULL,
+ &demo->swapchain_image_resources[i].uniform_buffer);
+ assert(!err);
+
+ vkGetBufferMemoryRequirements(demo->device,
+ demo->swapchain_image_resources[i].uniform_buffer,
+ &mem_reqs);
+
+ mem_alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+ mem_alloc.pNext = NULL;
+ mem_alloc.allocationSize = mem_reqs.size;
+ mem_alloc.memoryTypeIndex = 0;
+
+ pass = memory_type_from_properties(
+ demo, mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+ &mem_alloc.memoryTypeIndex);
+ assert(pass);
+
+ err = vkAllocateMemory(demo->device, &mem_alloc, NULL,
+ &demo->swapchain_image_resources[i].uniform_memory);
+ assert(!err);
+
+ err = vkMapMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory, 0,
+ VK_WHOLE_SIZE, 0, (void **)&pData);
+ assert(!err);
+
+ memcpy(pData, &data, sizeof data);
+
+ vkUnmapMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory);
+
+ err = vkBindBufferMemory(demo->device, demo->swapchain_image_resources[i].uniform_buffer,
+ demo->swapchain_image_resources[i].uniform_memory, 0);
+ assert(!err);
+ }
+}
+
+static void demo_prepare_descriptor_layout(struct demo *demo) {
+ const VkDescriptorSetLayoutBinding layout_bindings[2] = {
+ [0] =
+ {
+ .binding = 0,
+ .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+ .descriptorCount = 1,
+ .stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
+ .pImmutableSamplers = NULL,
+ },
+ [1] =
+ {
+ .binding = 1,
+ .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .descriptorCount = DEMO_TEXTURE_COUNT,
+ .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
+ .pImmutableSamplers = NULL,
+ },
+ };
+ const VkDescriptorSetLayoutCreateInfo descriptor_layout = {
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
+ .pNext = NULL,
+ .bindingCount = 2,
+ .pBindings = layout_bindings,
+ };
+ VkResult U_ASSERT_ONLY err;
+
+ err = vkCreateDescriptorSetLayout(demo->device, &descriptor_layout, NULL,
+ &demo->desc_layout);
+ assert(!err);
+
+ const VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+ .pNext = NULL,
+ .setLayoutCount = 1,
+ .pSetLayouts = &demo->desc_layout,
+ };
+
+ err = vkCreatePipelineLayout(demo->device, &pPipelineLayoutCreateInfo, NULL,
+ &demo->pipeline_layout);
+ assert(!err);
+}
+
+static void demo_prepare_render_pass(struct demo *demo) {
+ // The initial layout for the color and depth attachments will be LAYOUT_UNDEFINED
+ // because at the start of the renderpass, we don't care about their contents.
+ // At the start of the subpass, the color attachment's layout will be transitioned
+ // to LAYOUT_COLOR_ATTACHMENT_OPTIMAL and the depth stencil attachment's layout
+ // will be transitioned to LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL. At the end of
+ // the renderpass, the color attachment's layout will be transitioned to
+ // LAYOUT_PRESENT_SRC_KHR to be ready to present. This is all done as part of
+ // the renderpass, no barriers are necessary.
+ const VkAttachmentDescription attachments[2] = {
+ [0] =
+ {
+ .format = demo->format,
+ .flags = 0,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
+ .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+ },
+ [1] =
+ {
+ .format = demo->depth.format,
+ .flags = 0,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+ .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
+ .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
+ .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
+ .initialLayout =
+ VK_IMAGE_LAYOUT_UNDEFINED,
+ .finalLayout =
+ VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
+ },
+ };
+ const VkAttachmentReference color_reference = {
+ .attachment = 0, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ };
+ const VkAttachmentReference depth_reference = {
+ .attachment = 1,
+ .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
+ };
+ const VkSubpassDescription subpass = {
+ .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
+ .flags = 0,
+ .inputAttachmentCount = 0,
+ .pInputAttachments = NULL,
+ .colorAttachmentCount = 1,
+ .pColorAttachments = &color_reference,
+ .pResolveAttachments = NULL,
+ .pDepthStencilAttachment = &depth_reference,
+ .preserveAttachmentCount = 0,
+ .pPreserveAttachments = NULL,
+ };
+ const VkRenderPassCreateInfo rp_info = {
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+ .pNext = NULL,
+ .flags = 0,
+ .attachmentCount = 2,
+ .pAttachments = attachments,
+ .subpassCount = 1,
+ .pSubpasses = &subpass,
+ .dependencyCount = 0,
+ .pDependencies = NULL,
+ };
+ VkResult U_ASSERT_ONLY err;
+
+ err = vkCreateRenderPass(demo->device, &rp_info, NULL, &demo->render_pass);
+ assert(!err);
+}
+
+//TODO: Merge shader reading
+#ifndef __ANDROID__
+static VkShaderModule
+demo_prepare_shader_module(struct demo *demo, const void *code, size_t size) {
+ VkShaderModule module;
+ VkShaderModuleCreateInfo moduleCreateInfo;
+ VkResult U_ASSERT_ONLY err;
+
+ moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+ moduleCreateInfo.pNext = NULL;
+
+ moduleCreateInfo.codeSize = size;
+ moduleCreateInfo.pCode = code;
+ moduleCreateInfo.flags = 0;
+ err = vkCreateShaderModule(demo->device, &moduleCreateInfo, NULL, &module);
+ assert(!err);
+
+ return module;
+}
+
+char *demo_read_spv(const char *filename, size_t *psize) {
+ long int size;
+ size_t U_ASSERT_ONLY retval;
+ void *shader_code;
+
+#if (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK))
+ filename =[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: @(filename)].UTF8String;
+#endif
+
+ FILE *fp = fopen(filename, "rb");
+ if (!fp)
+ return NULL;
+
+ fseek(fp, 0L, SEEK_END);
+ size = ftell(fp);
+
+ fseek(fp, 0L, SEEK_SET);
+
+ shader_code = malloc(size);
+ retval = fread(shader_code, size, 1, fp);
+ assert(retval == 1);
+
+ *psize = size;
+
+ fclose(fp);
+ return shader_code;
+}
+#endif
+
+static VkShaderModule demo_prepare_vs(struct demo *demo) {
+#ifdef __ANDROID__
+ VkShaderModuleCreateInfo sh_info = {};
+ sh_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+
+#include "cube.vert.h"
+ sh_info.codeSize = sizeof(cube_vert);
+ sh_info.pCode = cube_vert;
+ VkResult U_ASSERT_ONLY err = vkCreateShaderModule(demo->device, &sh_info, NULL, &demo->vert_shader_module);
+ assert(!err);
+#else
+ void *vertShaderCode;
+ size_t size;
+
+ vertShaderCode = demo_read_spv("cube-vert.spv", &size);
+ if (!vertShaderCode) {
+ ERR_EXIT("Failed to load cube-vert.spv", "Load Shader Failure");
+ }
+
+ demo->vert_shader_module =
+ demo_prepare_shader_module(demo, vertShaderCode, size);
+
+ free(vertShaderCode);
+#endif
+
+ return demo->vert_shader_module;
+}
+
+static VkShaderModule demo_prepare_fs(struct demo *demo) {
+#ifdef __ANDROID__
+ VkShaderModuleCreateInfo sh_info = {};
+ sh_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+
+#include "cube.frag.h"
+ sh_info.codeSize = sizeof(cube_frag);
+ sh_info.pCode = cube_frag;
+ VkResult U_ASSERT_ONLY err = vkCreateShaderModule(demo->device, &sh_info, NULL, &demo->frag_shader_module);
+ assert(!err);
+#else
+ void *fragShaderCode;
+ size_t size;
+
+ fragShaderCode = demo_read_spv("cube-frag.spv", &size);
+ if (!fragShaderCode) {
+ ERR_EXIT("Failed to load cube-frag.spv", "Load Shader Failure");
+ }
+
+ demo->frag_shader_module =
+ demo_prepare_shader_module(demo, fragShaderCode, size);
+
+ free(fragShaderCode);
+#endif
+
+ return demo->frag_shader_module;
+}
+
+static void demo_prepare_pipeline(struct demo *demo) {
+ VkGraphicsPipelineCreateInfo pipeline;
+ VkPipelineCacheCreateInfo pipelineCache;
+ VkPipelineVertexInputStateCreateInfo vi;
+ VkPipelineInputAssemblyStateCreateInfo ia;
+ VkPipelineRasterizationStateCreateInfo rs;
+ VkPipelineColorBlendStateCreateInfo cb;
+ VkPipelineDepthStencilStateCreateInfo ds;
+ VkPipelineViewportStateCreateInfo vp;
+ VkPipelineMultisampleStateCreateInfo ms;
+ VkDynamicState dynamicStateEnables[VK_DYNAMIC_STATE_RANGE_SIZE];
+ VkPipelineDynamicStateCreateInfo dynamicState;
+ VkResult U_ASSERT_ONLY err;
+
+ memset(dynamicStateEnables, 0, sizeof dynamicStateEnables);
+ memset(&dynamicState, 0, sizeof dynamicState);
+ dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
+ dynamicState.pDynamicStates = dynamicStateEnables;
+
+ memset(&pipeline, 0, sizeof(pipeline));
+ pipeline.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
+ pipeline.layout = demo->pipeline_layout;
+
+ memset(&vi, 0, sizeof(vi));
+ vi.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+
+ memset(&ia, 0, sizeof(ia));
+ ia.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
+ ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+
+ memset(&rs, 0, sizeof(rs));
+ rs.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
+ rs.polygonMode = VK_POLYGON_MODE_FILL;
+ rs.cullMode = VK_CULL_MODE_BACK_BIT;
+ rs.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
+ rs.depthClampEnable = VK_FALSE;
+ rs.rasterizerDiscardEnable = VK_FALSE;
+ rs.depthBiasEnable = VK_FALSE;
+ rs.lineWidth = 1.0f;
+
+ memset(&cb, 0, sizeof(cb));
+ cb.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
+ VkPipelineColorBlendAttachmentState att_state[1];
+ memset(att_state, 0, sizeof(att_state));
+ att_state[0].colorWriteMask = 0xf;
+ att_state[0].blendEnable = VK_FALSE;
+ cb.attachmentCount = 1;
+ cb.pAttachments = att_state;
+
+ memset(&vp, 0, sizeof(vp));
+ vp.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
+ vp.viewportCount = 1;
+ dynamicStateEnables[dynamicState.dynamicStateCount++] =
+ VK_DYNAMIC_STATE_VIEWPORT;
+ vp.scissorCount = 1;
+ dynamicStateEnables[dynamicState.dynamicStateCount++] =
+ VK_DYNAMIC_STATE_SCISSOR;
+
+ memset(&ds, 0, sizeof(ds));
+ ds.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
+ ds.depthTestEnable = VK_TRUE;
+ ds.depthWriteEnable = VK_TRUE;
+ ds.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
+ ds.depthBoundsTestEnable = VK_FALSE;
+ ds.back.failOp = VK_STENCIL_OP_KEEP;
+ ds.back.passOp = VK_STENCIL_OP_KEEP;
+ ds.back.compareOp = VK_COMPARE_OP_ALWAYS;
+ ds.stencilTestEnable = VK_FALSE;
+ ds.front = ds.back;
+
+ memset(&ms, 0, sizeof(ms));
+ ms.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
+ ms.pSampleMask = NULL;
+ ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
+
+ // Two stages: vs and fs
+ pipeline.stageCount = 2;
+ VkPipelineShaderStageCreateInfo shaderStages[2];
+ memset(&shaderStages, 0, 2 * sizeof(VkPipelineShaderStageCreateInfo));
+
+ shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
+ shaderStages[0].module = demo_prepare_vs(demo);
+ shaderStages[0].pName = "main";
+
+ shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
+ shaderStages[1].module = demo_prepare_fs(demo);
+ shaderStages[1].pName = "main";
+
+ memset(&pipelineCache, 0, sizeof(pipelineCache));
+ pipelineCache.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
+
+ err = vkCreatePipelineCache(demo->device, &pipelineCache, NULL,
+ &demo->pipelineCache);
+ assert(!err);
+
+ pipeline.pVertexInputState = &vi;
+ pipeline.pInputAssemblyState = &ia;
+ pipeline.pRasterizationState = &rs;
+ pipeline.pColorBlendState = &cb;
+ pipeline.pMultisampleState = &ms;
+ pipeline.pViewportState = &vp;
+ pipeline.pDepthStencilState = &ds;
+ pipeline.pStages = shaderStages;
+ pipeline.renderPass = demo->render_pass;
+ pipeline.pDynamicState = &dynamicState;
+
+ pipeline.renderPass = demo->render_pass;
+
+ err = vkCreateGraphicsPipelines(demo->device, demo->pipelineCache, 1,
+ &pipeline, NULL, &demo->pipeline);
+ assert(!err);
+
+ vkDestroyShaderModule(demo->device, demo->frag_shader_module, NULL);
+ vkDestroyShaderModule(demo->device, demo->vert_shader_module, NULL);
+}
+
+static void demo_prepare_descriptor_pool(struct demo *demo) {
+ const VkDescriptorPoolSize type_counts[2] = {
+ [0] =
+ {
+ .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+ .descriptorCount = demo->swapchainImageCount,
+ },
+ [1] =
+ {
+ .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .descriptorCount = demo->swapchainImageCount * DEMO_TEXTURE_COUNT,
+ },
+ };
+ const VkDescriptorPoolCreateInfo descriptor_pool = {
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
+ .pNext = NULL,
+ .maxSets = demo->swapchainImageCount,
+ .poolSizeCount = 2,
+ .pPoolSizes = type_counts,
+ };
+ VkResult U_ASSERT_ONLY err;
+
+ err = vkCreateDescriptorPool(demo->device, &descriptor_pool, NULL,
+ &demo->desc_pool);
+ assert(!err);
+}
+
+static void demo_prepare_descriptor_set(struct demo *demo) {
+ VkDescriptorImageInfo tex_descs[DEMO_TEXTURE_COUNT];
+ VkWriteDescriptorSet writes[2];
+ VkResult U_ASSERT_ONLY err;
+
+ VkDescriptorSetAllocateInfo alloc_info = {
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
+ .pNext = NULL,
+ .descriptorPool = demo->desc_pool,
+ .descriptorSetCount = 1,
+ .pSetLayouts = &demo->desc_layout};
+
+ VkDescriptorBufferInfo buffer_info;
+ buffer_info.offset = 0;
+ buffer_info.range = sizeof(struct vktexcube_vs_uniform);
+
+ memset(&tex_descs, 0, sizeof(tex_descs));
+ for (unsigned int i = 0; i < DEMO_TEXTURE_COUNT; i++) {
+ tex_descs[i].sampler = demo->textures[i].sampler;
+ tex_descs[i].imageView = demo->textures[i].view;
+ tex_descs[i].imageLayout = VK_IMAGE_LAYOUT_GENERAL;
+ }
+
+ memset(&writes, 0, sizeof(writes));
+
+ writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ writes[0].descriptorCount = 1;
+ writes[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ writes[0].pBufferInfo = &buffer_info;
+
+ writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ writes[1].dstBinding = 1;
+ writes[1].descriptorCount = DEMO_TEXTURE_COUNT;
+ writes[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ writes[1].pImageInfo = tex_descs;
+
+ for (unsigned int i = 0; i < demo->swapchainImageCount; i++) {
+ err = vkAllocateDescriptorSets(demo->device, &alloc_info, &demo->swapchain_image_resources[i].descriptor_set);
+ assert(!err);
+ buffer_info.buffer = demo->swapchain_image_resources[i].uniform_buffer;
+ writes[0].dstSet = demo->swapchain_image_resources[i].descriptor_set;
+ writes[1].dstSet = demo->swapchain_image_resources[i].descriptor_set;
+ vkUpdateDescriptorSets(demo->device, 2, writes, 0, NULL);
+ }
+}
+
+static void demo_prepare_framebuffers(struct demo *demo) {
+ VkImageView attachments[2];
+ attachments[1] = demo->depth.view;
+
+ const VkFramebufferCreateInfo fb_info = {
+ .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
+ .pNext = NULL,
+ .renderPass = demo->render_pass,
+ .attachmentCount = 2,
+ .pAttachments = attachments,
+ .width = demo->width,
+ .height = demo->height,
+ .layers = 1,
+ };
+ VkResult U_ASSERT_ONLY err;
+ uint32_t i;
+
+ for (i = 0; i < demo->swapchainImageCount; i++) {
+ attachments[0] = demo->swapchain_image_resources[i].view;
+ err = vkCreateFramebuffer(demo->device, &fb_info, NULL,
+ &demo->swapchain_image_resources[i].framebuffer);
+ assert(!err);
+ }
+}
+
+static void demo_prepare(struct demo *demo) {
+ VkResult U_ASSERT_ONLY err;
+
+ const VkCommandPoolCreateInfo cmd_pool_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .pNext = NULL,
+ .queueFamilyIndex = demo->graphics_queue_family_index,
+ .flags = 0,
+ };
+ err = vkCreateCommandPool(demo->device, &cmd_pool_info, NULL,
+ &demo->cmd_pool);
+ assert(!err);
+
+ const VkCommandBufferAllocateInfo cmd = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .pNext = NULL,
+ .commandPool = demo->cmd_pool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = 1,
+ };
+ err = vkAllocateCommandBuffers(demo->device, &cmd, &demo->cmd);
+ assert(!err);
+ VkCommandBufferBeginInfo cmd_buf_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .pNext = NULL,
+ .flags = 0,
+ .pInheritanceInfo = NULL,
+ };
+ err = vkBeginCommandBuffer(demo->cmd, &cmd_buf_info);
+ assert(!err);
+
+ demo_prepare_buffers(demo);
+ demo_prepare_depth(demo);
+ demo_prepare_textures(demo);
+ demo_prepare_cube_data_buffers(demo);
+
+ demo_prepare_descriptor_layout(demo);
+ demo_prepare_render_pass(demo);
+ demo_prepare_pipeline(demo);
+
+ for (uint32_t i = 0; i < demo->swapchainImageCount; i++) {
+ err =
+ vkAllocateCommandBuffers(demo->device, &cmd, &demo->swapchain_image_resources[i].cmd);
+ assert(!err);
+ }
+
+ if (demo->separate_present_queue) {
+ const VkCommandPoolCreateInfo present_cmd_pool_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .pNext = NULL,
+ .queueFamilyIndex = demo->present_queue_family_index,
+ .flags = 0,
+ };
+ err = vkCreateCommandPool(demo->device, &present_cmd_pool_info, NULL,
+ &demo->present_cmd_pool);
+ assert(!err);
+ const VkCommandBufferAllocateInfo present_cmd_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .pNext = NULL,
+ .commandPool = demo->present_cmd_pool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = 1,
+ };
+ for (uint32_t i = 0; i < demo->swapchainImageCount; i++) {
+ err = vkAllocateCommandBuffers(
+ demo->device, &present_cmd_info, &demo->swapchain_image_resources[i].graphics_to_present_cmd);
+ assert(!err);
+ demo_build_image_ownership_cmd(demo, i);
+ }
+ }
+
+ demo_prepare_descriptor_pool(demo);
+ demo_prepare_descriptor_set(demo);
+
+ demo_prepare_framebuffers(demo);
+
+ for (uint32_t i = 0; i < demo->swapchainImageCount; i++) {
+ demo->current_buffer = i;
+ demo_draw_build_cmd(demo, demo->swapchain_image_resources[i].cmd);
+ }
+
+ /*
+ * Prepare functions above may generate pipeline commands
+ * that need to be flushed before beginning the render loop.
+ */
+ demo_flush_init_cmd(demo);
+ if (demo->staging_texture.image) {
+ demo_destroy_texture_image(demo, &demo->staging_texture);
+ }
+
+ demo->current_buffer = 0;
+ demo->prepared = true;
+}
+
+static void demo_cleanup(struct demo *demo) {
+ uint32_t i;
+
+ demo->prepared = false;
+ vkDeviceWaitIdle(demo->device);
+
+ // Wait for fences from present operations
+ for (i = 0; i < FRAME_LAG; i++) {
+ vkWaitForFences(demo->device, 1, &demo->fences[i], VK_TRUE, UINT64_MAX);
+ vkDestroyFence(demo->device, demo->fences[i], NULL);
+ vkDestroySemaphore(demo->device, demo->image_acquired_semaphores[i], NULL);
+ vkDestroySemaphore(demo->device, demo->draw_complete_semaphores[i], NULL);
+ if (demo->separate_present_queue) {
+ vkDestroySemaphore(demo->device, demo->image_ownership_semaphores[i], NULL);
+ }
+ }
+
+ for (i = 0; i < demo->swapchainImageCount; i++) {
+ vkDestroyFramebuffer(demo->device, demo->swapchain_image_resources[i].framebuffer, NULL);
+ }
+ vkDestroyDescriptorPool(demo->device, demo->desc_pool, NULL);
+
+ vkDestroyPipeline(demo->device, demo->pipeline, NULL);
+ vkDestroyPipelineCache(demo->device, demo->pipelineCache, NULL);
+ vkDestroyRenderPass(demo->device, demo->render_pass, NULL);
+ vkDestroyPipelineLayout(demo->device, demo->pipeline_layout, NULL);
+ vkDestroyDescriptorSetLayout(demo->device, demo->desc_layout, NULL);
+
+ for (i = 0; i < DEMO_TEXTURE_COUNT; i++) {
+ vkDestroyImageView(demo->device, demo->textures[i].view, NULL);
+ vkDestroyImage(demo->device, demo->textures[i].image, NULL);
+ vkFreeMemory(demo->device, demo->textures[i].mem, NULL);
+ vkDestroySampler(demo->device, demo->textures[i].sampler, NULL);
+ }
+ demo->fpDestroySwapchainKHR(demo->device, demo->swapchain, NULL);
+
+ vkDestroyImageView(demo->device, demo->depth.view, NULL);
+ vkDestroyImage(demo->device, demo->depth.image, NULL);
+ vkFreeMemory(demo->device, demo->depth.mem, NULL);
+
+ for (i = 0; i < demo->swapchainImageCount; i++) {
+ vkDestroyImageView(demo->device, demo->swapchain_image_resources[i].view, NULL);
+ vkFreeCommandBuffers(demo->device, demo->cmd_pool, 1,
+ &demo->swapchain_image_resources[i].cmd);
+ vkDestroyBuffer(demo->device, demo->swapchain_image_resources[i].uniform_buffer, NULL);
+ vkFreeMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory, NULL);
+ }
+ free(demo->swapchain_image_resources);
+ free(demo->queue_props);
+ vkDestroyCommandPool(demo->device, demo->cmd_pool, NULL);
+
+ if (demo->separate_present_queue) {
+ vkDestroyCommandPool(demo->device, demo->present_cmd_pool, NULL);
+ }
+ vkDeviceWaitIdle(demo->device);
+ vkDestroyDevice(demo->device, NULL);
+ if (demo->validate) {
+ demo->DestroyDebugReportCallback(demo->inst, demo->msg_callback, NULL);
+ }
+ vkDestroySurfaceKHR(demo->inst, demo->surface, NULL);
+ vkDestroyInstance(demo->inst, NULL);
+
+#if defined(VK_USE_PLATFORM_XLIB_KHR)
+ XDestroyWindow(demo->display, demo->xlib_window);
+ XCloseDisplay(demo->display);
+#elif defined(VK_USE_PLATFORM_XCB_KHR)
+ xcb_destroy_window(demo->connection, demo->xcb_window);
+ xcb_disconnect(demo->connection);
+ free(demo->atom_wm_delete_window);
+#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
+ wl_shell_surface_destroy(demo->shell_surface);
+ wl_surface_destroy(demo->window);
+ wl_shell_destroy(demo->shell);
+ wl_compositor_destroy(demo->compositor);
+ wl_registry_destroy(demo->registry);
+ wl_display_disconnect(demo->display);
+#elif defined(VK_USE_PLATFORM_MIR_KHR)
+#endif
+}
+
+static void demo_resize(struct demo *demo) {
+ uint32_t i;
+
+ // Don't react to resize until after first initialization.
+ if (!demo->prepared) {
+ return;
+ }
+ // In order to properly resize the window, we must re-create the swapchain
+ // AND redo the command buffers, etc.
+ //
+ // First, perform part of the demo_cleanup() function:
+ demo->prepared = false;
+ vkDeviceWaitIdle(demo->device);
+
+ for (i = 0; i < demo->swapchainImageCount; i++) {
+ vkDestroyFramebuffer(demo->device, demo->swapchain_image_resources[i].framebuffer, NULL);
+ }
+ vkDestroyDescriptorPool(demo->device, demo->desc_pool, NULL);
+
+ vkDestroyPipeline(demo->device, demo->pipeline, NULL);
+ vkDestroyPipelineCache(demo->device, demo->pipelineCache, NULL);
+ vkDestroyRenderPass(demo->device, demo->render_pass, NULL);
+ vkDestroyPipelineLayout(demo->device, demo->pipeline_layout, NULL);
+ vkDestroyDescriptorSetLayout(demo->device, demo->desc_layout, NULL);
+
+ for (i = 0; i < DEMO_TEXTURE_COUNT; i++) {
+ vkDestroyImageView(demo->device, demo->textures[i].view, NULL);
+ vkDestroyImage(demo->device, demo->textures[i].image, NULL);
+ vkFreeMemory(demo->device, demo->textures[i].mem, NULL);
+ vkDestroySampler(demo->device, demo->textures[i].sampler, NULL);
+ }
+
+ vkDestroyImageView(demo->device, demo->depth.view, NULL);
+ vkDestroyImage(demo->device, demo->depth.image, NULL);
+ vkFreeMemory(demo->device, demo->depth.mem, NULL);
+
+ for (i = 0; i < demo->swapchainImageCount; i++) {
+ vkDestroyImageView(demo->device, demo->swapchain_image_resources[i].view, NULL);
+ vkFreeCommandBuffers(demo->device, demo->cmd_pool, 1,
+ &demo->swapchain_image_resources[i].cmd);
+ vkDestroyBuffer(demo->device, demo->swapchain_image_resources[i].uniform_buffer, NULL);
+ vkFreeMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory, NULL);
+ }
+ vkDestroyCommandPool(demo->device, demo->cmd_pool, NULL);
+ if (demo->separate_present_queue) {
+ vkDestroyCommandPool(demo->device, demo->present_cmd_pool, NULL);
+ }
+ free(demo->swapchain_image_resources);
+
+ // Second, re-perform the demo_prepare() function, which will re-create the
+ // swapchain:
+ demo_prepare(demo);
+}
+
+// On MS-Windows, make this a global, so it's available to WndProc()
+struct demo demo;
+
+#if defined(VK_USE_PLATFORM_WIN32_KHR)
+static void demo_run(struct demo *demo) {
+ if (!demo->prepared)
+ return;
+
+ demo_draw(demo);
+ demo->curFrame++;
+ if (demo->frameCount != INT_MAX && demo->curFrame == demo->frameCount) {
+ PostQuitMessage(validation_error);
+ }
+}
+
+// MS-Windows event handling function:
+LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
+ switch (uMsg) {
+ case WM_CLOSE:
+ PostQuitMessage(validation_error);
+ break;
+ case WM_PAINT:
+ // The validation callback calls MessageBox which can generate paint
+ // events - don't make more Vulkan calls if we got here from the
+ // callback
+ if (!in_callback) {
+ demo_run(&demo);
+ }
+ break;
+ case WM_GETMINMAXINFO: // set window's minimum size
+ ((MINMAXINFO*)lParam)->ptMinTrackSize = demo.minsize;
+ return 0;
+ case WM_SIZE:
+ // Resize the application to the new window size, except when
+ // it was minimized. Vulkan doesn't support images or swapchains
+ // with width=0 and height=0.
+ if (wParam != SIZE_MINIMIZED) {
+ demo.width = lParam & 0xffff;
+ demo.height = (lParam & 0xffff0000) >> 16;
+ demo_resize(&demo);
+ }
+ break;
+ default:
+ break;
+ }
+ return (DefWindowProc(hWnd, uMsg, wParam, lParam));
+}
+
+static void demo_create_window(struct demo *demo) {
+ WNDCLASSEX win_class;
+
+ // Initialize the window class structure:
+ win_class.cbSize = sizeof(WNDCLASSEX);
+ win_class.style = CS_HREDRAW | CS_VREDRAW;
+ win_class.lpfnWndProc = WndProc;
+ win_class.cbClsExtra = 0;
+ win_class.cbWndExtra = 0;
+ win_class.hInstance = demo->connection; // hInstance
+ win_class.hIcon = LoadIcon(NULL, IDI_APPLICATION);
+ win_class.hCursor = LoadCursor(NULL, IDC_ARROW);
+ win_class.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
+ win_class.lpszMenuName = NULL;
+ win_class.lpszClassName = demo->name;
+ win_class.hIconSm = LoadIcon(NULL, IDI_WINLOGO);
+ // Register window class:
+ if (!RegisterClassEx(&win_class)) {
+ // It didn't work, so try to give a useful error:
+ printf("Unexpected error trying to start the application!\n");
+ fflush(stdout);
+ exit(1);
+ }
+ // Create window with the registered class:
+ RECT wr = {0, 0, demo->width, demo->height};
+ AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE);
+ demo->window = CreateWindowEx(0,
+ demo->name, // class name
+ demo->name, // app name
+ WS_OVERLAPPEDWINDOW | // window style
+ WS_VISIBLE | WS_SYSMENU,
+ 100, 100, // x/y coords
+ wr.right - wr.left, // width
+ wr.bottom - wr.top, // height
+ NULL, // handle to parent
+ NULL, // handle to menu
+ demo->connection, // hInstance
+ NULL); // no extra parameters
+ if (!demo->window) {
+ // It didn't work, so try to give a useful error:
+ printf("Cannot create a window in which to draw!\n");
+ fflush(stdout);
+ exit(1);
+ }
+ // Window client area size must be at least 1 pixel high, to prevent crash.
+ demo->minsize.x = GetSystemMetrics(SM_CXMINTRACK);
+ demo->minsize.y = GetSystemMetrics(SM_CYMINTRACK)+1;
+}
+#elif defined(VK_USE_PLATFORM_XLIB_KHR)
+static void demo_create_xlib_window(struct demo *demo) {
+
+ XInitThreads();
+ demo->display = XOpenDisplay(NULL);
+ long visualMask = VisualScreenMask;
+ int numberOfVisuals;
+ XVisualInfo vInfoTemplate={};
+ vInfoTemplate.screen = DefaultScreen(demo->display);
+ XVisualInfo *visualInfo = XGetVisualInfo(demo->display, visualMask,
+ &vInfoTemplate, &numberOfVisuals);
+
+ Colormap colormap = XCreateColormap(
+ demo->display, RootWindow(demo->display, vInfoTemplate.screen),
+ visualInfo->visual, AllocNone);
+
+ XSetWindowAttributes windowAttributes={};
+ windowAttributes.colormap = colormap;
+ windowAttributes.background_pixel = 0xFFFFFFFF;
+ windowAttributes.border_pixel = 0;
+ windowAttributes.event_mask =
+ KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask;
+
+ demo->xlib_window = XCreateWindow(
+ demo->display, RootWindow(demo->display, vInfoTemplate.screen), 0, 0,
+ demo->width, demo->height, 0, visualInfo->depth, InputOutput,
+ visualInfo->visual,
+ CWBackPixel | CWBorderPixel | CWEventMask | CWColormap, &windowAttributes);
+
+ XSelectInput(demo->display, demo->xlib_window, ExposureMask | KeyPressMask);
+ XMapWindow(demo->display, demo->xlib_window);
+ XFlush(demo->display);
+ demo->xlib_wm_delete_window =
+ XInternAtom(demo->display, "WM_DELETE_WINDOW", False);
+}
+static void demo_handle_xlib_event(struct demo *demo, const XEvent *event) {
+ switch(event->type) {
+ case ClientMessage:
+ if ((Atom)event->xclient.data.l[0] == demo->xlib_wm_delete_window)
+ demo->quit = true;
+ break;
+ case KeyPress:
+ switch (event->xkey.keycode) {
+ case 0x9: // Escape
+ demo->quit = true;
+ break;
+ case 0x71: // left arrow key
+ demo->spin_angle -= demo->spin_increment;
+ break;
+ case 0x72: // right arrow key
+ demo->spin_angle += demo->spin_increment;
+ break;
+ case 0x41: // space bar
+ demo->pause = !demo->pause;
+ break;
+ }
+ break;
+ case ConfigureNotify:
+ if ((demo->width != event->xconfigure.width) ||
+ (demo->height != event->xconfigure.height)) {
+ demo->width = event->xconfigure.width;
+ demo->height = event->xconfigure.height;
+ demo_resize(demo);
+ }
+ break;
+ default:
+ break;
+ }
+
+}
+
+static void demo_run_xlib(struct demo *demo) {
+
+ while (!demo->quit) {
+ XEvent event;
+
+ if (demo->pause) {
+ XNextEvent(demo->display, &event);
+ demo_handle_xlib_event(demo, &event);
+ }
+ while (XPending(demo->display) > 0) {
+ XNextEvent(demo->display, &event);
+ demo_handle_xlib_event(demo, &event);
+ }
+
+ demo_draw(demo);
+ demo->curFrame++;
+ if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount)
+ demo->quit = true;
+ }
+}
+#elif defined(VK_USE_PLATFORM_XCB_KHR)
+static void demo_handle_xcb_event(struct demo *demo,
+ const xcb_generic_event_t *event) {
+ uint8_t event_code = event->response_type & 0x7f;
+ switch (event_code) {
+ case XCB_EXPOSE:
+ // TODO: Resize window
+ break;
+ case XCB_CLIENT_MESSAGE:
+ if ((*(xcb_client_message_event_t *)event).data.data32[0] ==
+ (*demo->atom_wm_delete_window).atom) {
+ demo->quit = true;
+ }
+ break;
+ case XCB_KEY_RELEASE: {
+ const xcb_key_release_event_t *key =
+ (const xcb_key_release_event_t *)event;
+
+ switch (key->detail) {
+ case 0x9: // Escape
+ demo->quit = true;
+ break;
+ case 0x71: // left arrow key
+ demo->spin_angle -= demo->spin_increment;
+ break;
+ case 0x72: // right arrow key
+ demo->spin_angle += demo->spin_increment;
+ break;
+ case 0x41: // space bar
+ demo->pause = !demo->pause;
+ break;
+ }
+ } break;
+ case XCB_CONFIGURE_NOTIFY: {
+ const xcb_configure_notify_event_t *cfg =
+ (const xcb_configure_notify_event_t *)event;
+ if ((demo->width != cfg->width) || (demo->height != cfg->height)) {
+ demo->width = cfg->width;
+ demo->height = cfg->height;
+ demo_resize(demo);
+ }
+ } break;
+ default:
+ break;
+ }
+}
+
+static void demo_run_xcb(struct demo *demo) {
+ xcb_flush(demo->connection);
+
+ while (!demo->quit) {
+ xcb_generic_event_t *event;
+
+ if (demo->pause) {
+ event = xcb_wait_for_event(demo->connection);
+ }
+ else {
+ event = xcb_poll_for_event(demo->connection);
+ }
+ while (event) {
+ demo_handle_xcb_event(demo, event);
+ free(event);
+ event = xcb_poll_for_event(demo->connection);
+ }
+
+ demo_draw(demo);
+ demo->curFrame++;
+ if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount)
+ demo->quit = true;
+ }
+}
+
+static void demo_create_xcb_window(struct demo *demo) {
+ uint32_t value_mask, value_list[32];
+
+ demo->xcb_window = xcb_generate_id(demo->connection);
+
+ value_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
+ value_list[0] = demo->screen->black_pixel;
+ value_list[1] = XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_EXPOSURE |
+ XCB_EVENT_MASK_STRUCTURE_NOTIFY;
+
+ xcb_create_window(demo->connection, XCB_COPY_FROM_PARENT, demo->xcb_window,
+ demo->screen->root, 0, 0, demo->width, demo->height, 0,
+ XCB_WINDOW_CLASS_INPUT_OUTPUT, demo->screen->root_visual,
+ value_mask, value_list);
+
+ /* Magic code that will send notification when window is destroyed */
+ xcb_intern_atom_cookie_t cookie =
+ xcb_intern_atom(demo->connection, 1, 12, "WM_PROTOCOLS");
+ xcb_intern_atom_reply_t *reply =
+ xcb_intern_atom_reply(demo->connection, cookie, 0);
+
+ xcb_intern_atom_cookie_t cookie2 =
+ xcb_intern_atom(demo->connection, 0, 16, "WM_DELETE_WINDOW");
+ demo->atom_wm_delete_window =
+ xcb_intern_atom_reply(demo->connection, cookie2, 0);
+
+ xcb_change_property(demo->connection, XCB_PROP_MODE_REPLACE, demo->xcb_window,
+ (*reply).atom, 4, 32, 1,
+ &(*demo->atom_wm_delete_window).atom);
+ free(reply);
+
+ xcb_map_window(demo->connection, demo->xcb_window);
+
+ // Force the x/y coordinates to 100,100 results are identical in consecutive
+ // runs
+ const uint32_t coords[] = {100, 100};
+ xcb_configure_window(demo->connection, demo->xcb_window,
+ XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, coords);
+}
+// VK_USE_PLATFORM_XCB_KHR
+#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
+static void demo_run(struct demo *demo) {
+ while (!demo->quit) {
+ demo_draw(demo);
+ demo->curFrame++;
+ if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount)
+ demo->quit = true;
+ }
+}
+
+static void handle_ping(void *data UNUSED,
+ struct wl_shell_surface *shell_surface,
+ uint32_t serial) {
+ wl_shell_surface_pong(shell_surface, serial);
+}
+
+static void handle_configure(void *data UNUSED,
+ struct wl_shell_surface *shell_surface UNUSED,
+ uint32_t edges UNUSED, int32_t width UNUSED,
+ int32_t height UNUSED) {}
+
+static void handle_popup_done(void *data UNUSED,
+ struct wl_shell_surface *shell_surface UNUSED) {}
+
+static const struct wl_shell_surface_listener shell_surface_listener = {
+ handle_ping, handle_configure, handle_popup_done};
+
+static void demo_create_window(struct demo *demo) {
+ demo->window = wl_compositor_create_surface(demo->compositor);
+ if (!demo->window) {
+ printf("Can not create wayland_surface from compositor!\n");
+ fflush(stdout);
+ exit(1);
+ }
+
+ demo->shell_surface = wl_shell_get_shell_surface(demo->shell, demo->window);
+ if (!demo->shell_surface) {
+ printf("Can not get shell_surface from wayland_surface!\n");
+ fflush(stdout);
+ exit(1);
+ }
+ wl_shell_surface_add_listener(demo->shell_surface, &shell_surface_listener,
+ demo);
+ wl_shell_surface_set_toplevel(demo->shell_surface);
+ wl_shell_surface_set_title(demo->shell_surface, APP_SHORT_NAME);
+}
+#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
+static void demo_run(struct demo *demo) {
+ if (!demo->prepared)
+ return;
+
+ demo_draw(demo);
+ demo->curFrame++;
+}
+#elif defined(VK_USE_PLATFORM_MIR_KHR)
+#elif defined(VK_USE_PLATFORM_DISPLAY_KHR)
+static VkResult demo_create_display_surface(struct demo *demo) {
+ VkResult U_ASSERT_ONLY err;
+ uint32_t display_count;
+ uint32_t mode_count;
+ uint32_t plane_count;
+ VkDisplayPropertiesKHR display_props;
+ VkDisplayKHR display;
+ VkDisplayModePropertiesKHR mode_props;
+ VkDisplayPlanePropertiesKHR *plane_props;
+ VkBool32 found_plane = VK_FALSE;
+ uint32_t plane_index;
+ VkExtent2D image_extent;
+ VkDisplaySurfaceCreateInfoKHR create_info;
+
+ // Get the first display
+ err = vkGetPhysicalDeviceDisplayPropertiesKHR(demo->gpu, &display_count, NULL);
+ assert(!err);
+
+ if (display_count == 0) {
+ printf("Cannot find any display!\n");
+ fflush(stdout);
+ exit(1);
+ }
+
+ display_count = 1;
+ err = vkGetPhysicalDeviceDisplayPropertiesKHR(demo->gpu, &display_count, &display_props);
+ assert(!err || (err == VK_INCOMPLETE));
+
+ display = display_props.display;
+
+ // Get the first mode of the display
+ err = vkGetDisplayModePropertiesKHR(demo->gpu, display, &mode_count, NULL);
+ assert(!err);
+
+ if (mode_count == 0) {
+ printf("Cannot find any mode for the display!\n");
+ fflush(stdout);
+ exit(1);
+ }
+
+ mode_count = 1;
+ err = vkGetDisplayModePropertiesKHR(demo->gpu, display, &mode_count, &mode_props);
+ assert(!err || (err == VK_INCOMPLETE));
+
+ // Get the list of planes
+ err = vkGetPhysicalDeviceDisplayPlanePropertiesKHR(demo->gpu, &plane_count, NULL);
+ assert(!err);
+
+ if (plane_count == 0) {
+ printf("Cannot find any plane!\n");
+ fflush(stdout);
+ exit(1);
+ }
+
+ plane_props = malloc(sizeof(VkDisplayPlanePropertiesKHR) * plane_count);
+ assert(plane_props);
+
+ err = vkGetPhysicalDeviceDisplayPlanePropertiesKHR(demo->gpu, &plane_count, plane_props);
+ assert(!err);
+
+ // Find a plane compatible with the display
+ for (plane_index = 0; plane_index < plane_count; plane_index++) {
+ uint32_t supported_count;
+ VkDisplayKHR *supported_displays;
+
+ // Disqualify planes that are bound to a different display
+ if ((plane_props[plane_index].currentDisplay != VK_NULL_HANDLE) &&
+ (plane_props[plane_index].currentDisplay != display)) {
+ continue;
+ }
+
+ err = vkGetDisplayPlaneSupportedDisplaysKHR(demo->gpu, plane_index, &supported_count, NULL);
+ assert(!err);
+
+ if (supported_count == 0) {
+ continue;
+ }
+
+ supported_displays = malloc(sizeof(VkDisplayKHR) * supported_count);
+ assert(supported_displays);
+
+ err = vkGetDisplayPlaneSupportedDisplaysKHR(demo->gpu, plane_index, &supported_count, supported_displays);
+ assert(!err);
+
+ for (uint32_t i = 0; i < supported_count; i++) {
+ if (supported_displays[i] == display) {
+ found_plane = VK_TRUE;
+ break;
+ }
+ }
+
+ free(supported_displays);
+
+ if (found_plane) {
+ break;
+ }
+ }
+
+ if (!found_plane) {
+ printf("Cannot find a plane compatible with the display!\n");
+ fflush(stdout);
+ exit(1);
+ }
+
+ free(plane_props);
+
+ VkDisplayPlaneCapabilitiesKHR planeCaps;
+ vkGetDisplayPlaneCapabilitiesKHR(demo->gpu, mode_props.displayMode, plane_index, &planeCaps);
+ // Find a supported alpha mode
+ VkCompositeAlphaFlagBitsKHR alphaMode = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR;
+ VkCompositeAlphaFlagBitsKHR alphaModes[4] = {
+ VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR,
+ VK_DISPLAY_PLANE_ALPHA_GLOBAL_BIT_KHR,
+ VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR,
+ VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_PREMULTIPLIED_BIT_KHR,
+ };
+ for (uint32_t i = 0; i < sizeof(alphaModes); i++) {
+ if (planeCaps.supportedAlpha & alphaModes[i]) {
+ alphaMode = alphaModes[i];
+ break;
+ }
+ }
+ image_extent.width = mode_props.parameters.visibleRegion.width;
+ image_extent.height = mode_props.parameters.visibleRegion.height;
+
+ create_info.sType = VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR;
+ create_info.pNext = NULL;
+ create_info.flags = 0;
+ create_info.displayMode = mode_props.displayMode;
+ create_info.planeIndex = plane_index;
+ create_info.planeStackIndex = plane_props[plane_index].currentStackIndex;
+ create_info.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
+ create_info.alphaMode = alphaMode;
+ create_info.globalAlpha = 1.0f;
+ create_info.imageExtent = image_extent;
+
+ return vkCreateDisplayPlaneSurfaceKHR(demo->inst, &create_info, NULL, &demo->surface);
+}
+
+static void demo_run_display(struct demo *demo)
+{
+ while (!demo->quit) {
+ demo_draw(demo);
+ demo->curFrame++;
+
+ if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) {
+ demo->quit = true;
+ }
+ }
+}
+#endif
+
+/*
+ * Return 1 (true) if all layer names specified in check_names
+ * can be found in given layer properties.
+ */
+static VkBool32 demo_check_layers(uint32_t check_count, char **check_names,
+ uint32_t layer_count,
+ VkLayerProperties *layers) {
+ for (uint32_t i = 0; i < check_count; i++) {
+ VkBool32 found = 0;
+ for (uint32_t j = 0; j < layer_count; j++) {
+ if (!strcmp(check_names[i], layers[j].layerName)) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ fprintf(stderr, "Cannot find layer: %s\n", check_names[i]);
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static void demo_init_vk(struct demo *demo) {
+ VkResult err;
+ uint32_t instance_extension_count = 0;
+ uint32_t instance_layer_count = 0;
+ uint32_t validation_layer_count = 0;
+ char **instance_validation_layers = NULL;
+ demo->enabled_extension_count = 0;
+ demo->enabled_layer_count = 0;
+
+ char *instance_validation_layers_alt1[] = {
+ "VK_LAYER_LUNARG_standard_validation"
+ };
+
+ char *instance_validation_layers_alt2[] = {
+ "VK_LAYER_GOOGLE_threading", "VK_LAYER_LUNARG_parameter_validation",
+ "VK_LAYER_LUNARG_object_tracker", "VK_LAYER_LUNARG_core_validation",
+ "VK_LAYER_LUNARG_swapchain", "VK_LAYER_GOOGLE_unique_objects"
+ };
+
+ /* Look for validation layers */
+ VkBool32 validation_found = 0;
+ if (demo->validate) {
+
+ err = vkEnumerateInstanceLayerProperties(&instance_layer_count, NULL);
+ assert(!err);
+
+ instance_validation_layers = instance_validation_layers_alt1;
+ if (instance_layer_count > 0) {
+ VkLayerProperties *instance_layers =
+ malloc(sizeof (VkLayerProperties) * instance_layer_count);
+ err = vkEnumerateInstanceLayerProperties(&instance_layer_count,
+ instance_layers);
+ assert(!err);
+
+
+ validation_found = demo_check_layers(
+ ARRAY_SIZE(instance_validation_layers_alt1),
+ instance_validation_layers, instance_layer_count,
+ instance_layers);
+ if (validation_found) {
+ demo->enabled_layer_count = ARRAY_SIZE(instance_validation_layers_alt1);
+ demo->enabled_layers[0] = "VK_LAYER_LUNARG_standard_validation";
+ validation_layer_count = 1;
+ } else {
+ // use alternative set of validation layers
+ instance_validation_layers = instance_validation_layers_alt2;
+ demo->enabled_layer_count = ARRAY_SIZE(instance_validation_layers_alt2);
+ validation_found = demo_check_layers(
+ ARRAY_SIZE(instance_validation_layers_alt2),
+ instance_validation_layers, instance_layer_count,
+ instance_layers);
+ validation_layer_count =
+ ARRAY_SIZE(instance_validation_layers_alt2);
+ for (uint32_t i = 0; i < validation_layer_count; i++) {
+ demo->enabled_layers[i] = instance_validation_layers[i];
+ }
+ }
+ free(instance_layers);
+ }
+
+ if (!validation_found) {
+ ERR_EXIT("vkEnumerateInstanceLayerProperties failed to find "
+ "required validation layer.\n\n"
+ "Please look at the Getting Started guide for additional "
+ "information.\n",
+ "vkCreateInstance Failure");
+ }
+ }
+
+ /* Look for instance extensions */
+ VkBool32 surfaceExtFound = 0;
+ VkBool32 platformSurfaceExtFound = 0;
+ memset(demo->extension_names, 0, sizeof(demo->extension_names));
+
+ err = vkEnumerateInstanceExtensionProperties(
+ NULL, &instance_extension_count, NULL);
+ assert(!err);
+
+ if (instance_extension_count > 0) {
+ VkExtensionProperties *instance_extensions =
+ malloc(sizeof(VkExtensionProperties) * instance_extension_count);
+ err = vkEnumerateInstanceExtensionProperties(
+ NULL, &instance_extension_count, instance_extensions);
+ assert(!err);
+ for (uint32_t i = 0; i < instance_extension_count; i++) {
+ if (!strcmp(VK_KHR_SURFACE_EXTENSION_NAME,
+ instance_extensions[i].extensionName)) {
+ surfaceExtFound = 1;
+ demo->extension_names[demo->enabled_extension_count++] =
+ VK_KHR_SURFACE_EXTENSION_NAME;
+ }
+#if defined(VK_USE_PLATFORM_WIN32_KHR)
+ if (!strcmp(VK_KHR_WIN32_SURFACE_EXTENSION_NAME,
+ instance_extensions[i].extensionName)) {
+ platformSurfaceExtFound = 1;
+ demo->extension_names[demo->enabled_extension_count++] =
+ VK_KHR_WIN32_SURFACE_EXTENSION_NAME;
+ }
+#elif defined(VK_USE_PLATFORM_XLIB_KHR)
+ if (!strcmp(VK_KHR_XLIB_SURFACE_EXTENSION_NAME,
+ instance_extensions[i].extensionName)) {
+ platformSurfaceExtFound = 1;
+ demo->extension_names[demo->enabled_extension_count++] =
+ VK_KHR_XLIB_SURFACE_EXTENSION_NAME;
+ }
+#elif defined(VK_USE_PLATFORM_XCB_KHR)
+ if (!strcmp(VK_KHR_XCB_SURFACE_EXTENSION_NAME,
+ instance_extensions[i].extensionName)) {
+ platformSurfaceExtFound = 1;
+ demo->extension_names[demo->enabled_extension_count++] =
+ VK_KHR_XCB_SURFACE_EXTENSION_NAME;
+ }
+#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
+ if (!strcmp(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME,
+ instance_extensions[i].extensionName)) {
+ platformSurfaceExtFound = 1;
+ demo->extension_names[demo->enabled_extension_count++] =
+ VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME;
+ }
+#elif defined(VK_USE_PLATFORM_MIR_KHR)
+#elif defined(VK_USE_PLATFORM_DISPLAY_KHR)
+ if (!strcmp(VK_KHR_DISPLAY_EXTENSION_NAME,
+ instance_extensions[i].extensionName)) {
+ platformSurfaceExtFound = 1;
+ demo->extension_names[demo->enabled_extension_count++] =
+ VK_KHR_DISPLAY_EXTENSION_NAME;
+ }
+#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
+ if (!strcmp(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME,
+ instance_extensions[i].extensionName)) {
+ platformSurfaceExtFound = 1;
+ demo->extension_names[demo->enabled_extension_count++] =
+ VK_KHR_ANDROID_SURFACE_EXTENSION_NAME;
+ }
+#elif defined(VK_USE_PLATFORM_IOS_MVK)
+ if (!strcmp(VK_MVK_IOS_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) {
+ platformSurfaceExtFound = 1;
+ demo->extension_names[demo->enabled_extension_count++] = VK_MVK_IOS_SURFACE_EXTENSION_NAME;
+ }
+#elif defined(VK_USE_PLATFORM_MACOS_MVK)
+ if (!strcmp(VK_MVK_MACOS_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) {
+ platformSurfaceExtFound = 1;
+ demo->extension_names[demo->enabled_extension_count++] = VK_MVK_MACOS_SURFACE_EXTENSION_NAME;
+ }
+#endif
+ if (!strcmp(VK_EXT_DEBUG_REPORT_EXTENSION_NAME,
+ instance_extensions[i].extensionName)) {
+ if (demo->validate) {
+ demo->extension_names[demo->enabled_extension_count++] =
+ VK_EXT_DEBUG_REPORT_EXTENSION_NAME;
+ }
+ }
+ assert(demo->enabled_extension_count < 64);
+ }
+
+ free(instance_extensions);
+ }
+
+ if (!surfaceExtFound) {
+ ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find "
+ "the " VK_KHR_SURFACE_EXTENSION_NAME
+ " extension.\n\nDo you have a compatible "
+ "Vulkan installable client driver (ICD) installed?\nPlease "
+ "look at the Getting Started guide for additional "
+ "information.\n",
+ "vkCreateInstance Failure");
+ }
+ if (!platformSurfaceExtFound) {
+#if defined(VK_USE_PLATFORM_WIN32_KHR)
+ ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find "
+ "the " VK_KHR_WIN32_SURFACE_EXTENSION_NAME
+ " extension.\n\nDo you have a compatible "
+ "Vulkan installable client driver (ICD) installed?\nPlease "
+ "look at the Getting Started guide for additional "
+ "information.\n",
+ "vkCreateInstance Failure");
+#elif defined(VK_USE_PLATFORM_IOS_MVK)
+ ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the "
+ VK_MVK_IOS_SURFACE_EXTENSION_NAME" extension.\n\nDo you have a compatible "
+ "Vulkan installable client driver (ICD) installed?\nPlease "
+ "look at the Getting Started guide for additional "
+ "information.\n",
+ "vkCreateInstance Failure");
+#elif defined(VK_USE_PLATFORM_MACOS_MVK)
+ ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the "
+ VK_MVK_MACOS_SURFACE_EXTENSION_NAME" extension.\n\nDo you have a compatible "
+ "Vulkan installable client driver (ICD) installed?\nPlease "
+ "look at the Getting Started guide for additional "
+ "information.\n",
+ "vkCreateInstance Failure");
+#elif defined(VK_USE_PLATFORM_XCB_KHR)
+ ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find "
+ "the " VK_KHR_XCB_SURFACE_EXTENSION_NAME
+ " extension.\n\nDo you have a compatible "
+ "Vulkan installable client driver (ICD) installed?\nPlease "
+ "look at the Getting Started guide for additional "
+ "information.\n",
+ "vkCreateInstance Failure");
+#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
+ ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find "
+ "the " VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME
+ " extension.\n\nDo you have a compatible "
+ "Vulkan installable client driver (ICD) installed?\nPlease "
+ "look at the Getting Started guide for additional "
+ "information.\n",
+ "vkCreateInstance Failure");
+#elif defined(VK_USE_PLATFORM_MIR_KHR)
+#elif defined(VK_USE_PLATFORM_DISPLAY_KHR)
+ ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find "
+ "the " VK_KHR_DISPLAY_EXTENSION_NAME
+ " extension.\n\nDo you have a compatible "
+ "Vulkan installable client driver (ICD) installed?\nPlease "
+ "look at the Getting Started guide for additional "
+ "information.\n",
+ "vkCreateInstance Failure");
+#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
+ ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find "
+ "the " VK_KHR_ANDROID_SURFACE_EXTENSION_NAME
+ " extension.\n\nDo you have a compatible "
+ "Vulkan installable client driver (ICD) installed?\nPlease "
+ "look at the Getting Started guide for additional "
+ "information.\n",
+ "vkCreateInstance Failure");
+#elif defined(VK_USE_PLATFORM_XLIB_KHR)
+ ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find "
+ "the " VK_KHR_XLIB_SURFACE_EXTENSION_NAME
+ " extension.\n\nDo you have a compatible "
+ "Vulkan installable client driver (ICD) installed?\nPlease "
+ "look at the Getting Started guide for additional "
+ "information.\n",
+ "vkCreateInstance Failure");
+#endif
+ }
+ const VkApplicationInfo app = {
+ .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
+ .pNext = NULL,
+ .pApplicationName = APP_SHORT_NAME,
+ .applicationVersion = 0,
+ .pEngineName = APP_SHORT_NAME,
+ .engineVersion = 0,
+ .apiVersion = VK_API_VERSION_1_0,
+ };
+ VkInstanceCreateInfo inst_info = {
+ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ .pNext = NULL,
+ .pApplicationInfo = &app,
+ .enabledLayerCount = demo->enabled_layer_count,
+ .ppEnabledLayerNames = (const char *const *)instance_validation_layers,
+ .enabledExtensionCount = demo->enabled_extension_count,
+ .ppEnabledExtensionNames = (const char *const *)demo->extension_names,
+ };
+
+ /*
+ * This is info for a temp callback to use during CreateInstance.
+ * After the instance is created, we use the instance-based
+ * function to register the final callback.
+ */
+ VkDebugReportCallbackCreateInfoEXT dbgCreateInfoTemp;
+ VkValidationFlagsEXT val_flags;
+ if (demo->validate) {
+ dbgCreateInfoTemp.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT;
+ dbgCreateInfoTemp.pNext = NULL;
+ dbgCreateInfoTemp.pfnCallback = demo->use_break ? BreakCallback : dbgFunc;
+ dbgCreateInfoTemp.pUserData = demo;
+ dbgCreateInfoTemp.flags =
+ VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT;
+ if (demo->validate_checks_disabled) {
+ val_flags.sType = VK_STRUCTURE_TYPE_VALIDATION_FLAGS_EXT;
+ val_flags.pNext = NULL;
+ val_flags.disabledValidationCheckCount = 1;
+ VkValidationCheckEXT disabled_check = VK_VALIDATION_CHECK_ALL_EXT;
+ val_flags.pDisabledValidationChecks = &disabled_check;
+ dbgCreateInfoTemp.pNext = (void*)&val_flags;
+ }
+ inst_info.pNext = &dbgCreateInfoTemp;
+ }
+
+ uint32_t gpu_count;
+
+ err = vkCreateInstance(&inst_info, NULL, &demo->inst);
+ if (err == VK_ERROR_INCOMPATIBLE_DRIVER) {
+ ERR_EXIT("Cannot find a compatible Vulkan installable client driver "
+ "(ICD).\n\nPlease look at the Getting Started guide for "
+ "additional information.\n",
+ "vkCreateInstance Failure");
+ } else if (err == VK_ERROR_EXTENSION_NOT_PRESENT) {
+ ERR_EXIT("Cannot find a specified extension library"
+ ".\nMake sure your layers path is set appropriately.\n",
+ "vkCreateInstance Failure");
+ } else if (err) {
+ ERR_EXIT("vkCreateInstance failed.\n\nDo you have a compatible Vulkan "
+ "installable client driver (ICD) installed?\nPlease look at "
+ "the Getting Started guide for additional information.\n",
+ "vkCreateInstance Failure");
+ }
+
+ /* Make initial call to query gpu_count, then second call for gpu info*/
+ err = vkEnumeratePhysicalDevices(demo->inst, &gpu_count, NULL);
+ assert(!err && gpu_count > 0);
+
+ if (gpu_count > 0) {
+ VkPhysicalDevice *physical_devices = malloc(sizeof(VkPhysicalDevice) * gpu_count);
+ err = vkEnumeratePhysicalDevices(demo->inst, &gpu_count, physical_devices);
+ assert(!err);
+ /* For cube demo we just grab the first physical device */
+ demo->gpu = physical_devices[0];
+ free(physical_devices);
+ } else {
+ ERR_EXIT("vkEnumeratePhysicalDevices reported zero accessible devices.\n\n"
+ "Do you have a compatible Vulkan installable client driver (ICD) "
+ "installed?\nPlease look at the Getting Started guide for "
+ "additional information.\n",
+ "vkEnumeratePhysicalDevices Failure");
+ }
+
+ /* Look for device extensions */
+ uint32_t device_extension_count = 0;
+ VkBool32 swapchainExtFound = 0;
+ demo->enabled_extension_count = 0;
+ memset(demo->extension_names, 0, sizeof(demo->extension_names));
+
+ err = vkEnumerateDeviceExtensionProperties(demo->gpu, NULL,
+ &device_extension_count, NULL);
+ assert(!err);
+
+ if (device_extension_count > 0) {
+ VkExtensionProperties *device_extensions =
+ malloc(sizeof(VkExtensionProperties) * device_extension_count);
+ err = vkEnumerateDeviceExtensionProperties(
+ demo->gpu, NULL, &device_extension_count, device_extensions);
+ assert(!err);
+
+ for (uint32_t i = 0; i < device_extension_count; i++) {
+ if (!strcmp(VK_KHR_SWAPCHAIN_EXTENSION_NAME,
+ device_extensions[i].extensionName)) {
+ swapchainExtFound = 1;
+ demo->extension_names[demo->enabled_extension_count++] =
+ VK_KHR_SWAPCHAIN_EXTENSION_NAME;
+ }
+ assert(demo->enabled_extension_count < 64);
+ }
+
+ if (demo->VK_KHR_incremental_present_enabled) {
+ // Even though the user "enabled" the extension via the command
+ // line, we must make sure that it's enumerated for use with the
+ // device. Therefore, disable it here, and re-enable it again if
+ // enumerated.
+ demo->VK_KHR_incremental_present_enabled = false;
+ for (uint32_t i = 0; i < device_extension_count; i++) {
+ if (!strcmp(VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME,
+ device_extensions[i].extensionName)) {
+ demo->extension_names[demo->enabled_extension_count++] =
+ VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME;
+ demo->VK_KHR_incremental_present_enabled = true;
+ DbgMsg("VK_KHR_incremental_present extension enabled\n");
+ }
+ assert(demo->enabled_extension_count < 64);
+ }
+ if (!demo->VK_KHR_incremental_present_enabled) {
+ DbgMsg("VK_KHR_incremental_present extension NOT AVAILABLE\n");
+ }
+ }
+
+ if (demo->VK_GOOGLE_display_timing_enabled) {
+ // Even though the user "enabled" the extension via the command
+ // line, we must make sure that it's enumerated for use with the
+ // device. Therefore, disable it here, and re-enable it again if
+ // enumerated.
+ demo->VK_GOOGLE_display_timing_enabled = false;
+ for (uint32_t i = 0; i < device_extension_count; i++) {
+ if (!strcmp(VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME,
+ device_extensions[i].extensionName)) {
+ demo->extension_names[demo->enabled_extension_count++] =
+ VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME;
+ demo->VK_GOOGLE_display_timing_enabled = true;
+ DbgMsg("VK_GOOGLE_display_timing extension enabled\n");
+ }
+ assert(demo->enabled_extension_count < 64);
+ }
+ if (!demo->VK_GOOGLE_display_timing_enabled) {
+ DbgMsg("VK_GOOGLE_display_timing extension NOT AVAILABLE\n");
+ }
+ }
+
+ free(device_extensions);
+ }
+
+ if (!swapchainExtFound) {
+ ERR_EXIT("vkEnumerateDeviceExtensionProperties failed to find "
+ "the " VK_KHR_SWAPCHAIN_EXTENSION_NAME
+ " extension.\n\nDo you have a compatible "
+ "Vulkan installable client driver (ICD) installed?\nPlease "
+ "look at the Getting Started guide for additional "
+ "information.\n",
+ "vkCreateInstance Failure");
+ }
+
+ if (demo->validate) {
+ demo->CreateDebugReportCallback =
+ (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(
+ demo->inst, "vkCreateDebugReportCallbackEXT");
+ demo->DestroyDebugReportCallback =
+ (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(
+ demo->inst, "vkDestroyDebugReportCallbackEXT");
+ if (!demo->CreateDebugReportCallback) {
+ ERR_EXIT(
+ "GetProcAddr: Unable to find vkCreateDebugReportCallbackEXT\n",
+ "vkGetProcAddr Failure");
+ }
+ if (!demo->DestroyDebugReportCallback) {
+ ERR_EXIT(
+ "GetProcAddr: Unable to find vkDestroyDebugReportCallbackEXT\n",
+ "vkGetProcAddr Failure");
+ }
+ demo->DebugReportMessage =
+ (PFN_vkDebugReportMessageEXT)vkGetInstanceProcAddr(
+ demo->inst, "vkDebugReportMessageEXT");
+ if (!demo->DebugReportMessage) {
+ ERR_EXIT("GetProcAddr: Unable to find vkDebugReportMessageEXT\n",
+ "vkGetProcAddr Failure");
+ }
+
+ VkDebugReportCallbackCreateInfoEXT dbgCreateInfo;
+ PFN_vkDebugReportCallbackEXT callback;
+ callback = demo->use_break ? BreakCallback : dbgFunc;
+ dbgCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT;
+ dbgCreateInfo.pNext = NULL;
+ dbgCreateInfo.pfnCallback = callback;
+ dbgCreateInfo.pUserData = demo;
+ dbgCreateInfo.flags =
+ VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT;
+ err = demo->CreateDebugReportCallback(demo->inst, &dbgCreateInfo, NULL,
+ &demo->msg_callback);
+ switch (err) {
+ case VK_SUCCESS:
+ break;
+ case VK_ERROR_OUT_OF_HOST_MEMORY:
+ ERR_EXIT("CreateDebugReportCallback: out of host memory\n",
+ "CreateDebugReportCallback Failure");
+ break;
+ default:
+ ERR_EXIT("CreateDebugReportCallback: unknown failure\n",
+ "CreateDebugReportCallback Failure");
+ break;
+ }
+ }
+ vkGetPhysicalDeviceProperties(demo->gpu, &demo->gpu_props);
+
+ /* Call with NULL data to get count */
+ vkGetPhysicalDeviceQueueFamilyProperties(demo->gpu,
+ &demo->queue_family_count, NULL);
+ assert(demo->queue_family_count >= 1);
+
+ demo->queue_props = (VkQueueFamilyProperties *)malloc(
+ demo->queue_family_count * sizeof(VkQueueFamilyProperties));
+ vkGetPhysicalDeviceQueueFamilyProperties(
+ demo->gpu, &demo->queue_family_count, demo->queue_props);
+
+ // Query fine-grained feature support for this device.
+ // If app has specific feature requirements it should check supported
+ // features based on this query
+ VkPhysicalDeviceFeatures physDevFeatures;
+ vkGetPhysicalDeviceFeatures(demo->gpu, &physDevFeatures);
+
+ GET_INSTANCE_PROC_ADDR(demo->inst, GetPhysicalDeviceSurfaceSupportKHR);
+ GET_INSTANCE_PROC_ADDR(demo->inst, GetPhysicalDeviceSurfaceCapabilitiesKHR);
+ GET_INSTANCE_PROC_ADDR(demo->inst, GetPhysicalDeviceSurfaceFormatsKHR);
+ GET_INSTANCE_PROC_ADDR(demo->inst, GetPhysicalDeviceSurfacePresentModesKHR);
+ GET_INSTANCE_PROC_ADDR(demo->inst, GetSwapchainImagesKHR);
+}
+
+static void demo_create_device(struct demo *demo) {
+ VkResult U_ASSERT_ONLY err;
+ float queue_priorities[1] = {0.0};
+ VkDeviceQueueCreateInfo queues[2];
+ queues[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ queues[0].pNext = NULL;
+ queues[0].queueFamilyIndex = demo->graphics_queue_family_index;
+ queues[0].queueCount = 1;
+ queues[0].pQueuePriorities = queue_priorities;
+ queues[0].flags = 0;
+
+ VkDeviceCreateInfo device = {
+ .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+ .pNext = NULL,
+ .queueCreateInfoCount = 1,
+ .pQueueCreateInfos = queues,
+ .enabledLayerCount = 0,
+ .ppEnabledLayerNames = NULL,
+ .enabledExtensionCount = demo->enabled_extension_count,
+ .ppEnabledExtensionNames = (const char *const *)demo->extension_names,
+ .pEnabledFeatures =
+ NULL, // If specific features are required, pass them in here
+ };
+ if (demo->separate_present_queue) {
+ queues[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ queues[1].pNext = NULL;
+ queues[1].queueFamilyIndex = demo->present_queue_family_index;
+ queues[1].queueCount = 1;
+ queues[1].pQueuePriorities = queue_priorities;
+ queues[1].flags = 0;
+ device.queueCreateInfoCount = 2;
+ }
+ err = vkCreateDevice(demo->gpu, &device, NULL, &demo->device);
+ assert(!err);
+}
+
+static void demo_init_vk_swapchain(struct demo *demo) {
+ VkResult U_ASSERT_ONLY err;
+
+// Create a WSI surface for the window:
+#if defined(VK_USE_PLATFORM_WIN32_KHR)
+ VkWin32SurfaceCreateInfoKHR createInfo;
+ createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
+ createInfo.pNext = NULL;
+ createInfo.flags = 0;
+ createInfo.hinstance = demo->connection;
+ createInfo.hwnd = demo->window;
+
+ err =
+ vkCreateWin32SurfaceKHR(demo->inst, &createInfo, NULL, &demo->surface);
+#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
+ VkWaylandSurfaceCreateInfoKHR createInfo;
+ createInfo.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
+ createInfo.pNext = NULL;
+ createInfo.flags = 0;
+ createInfo.display = demo->display;
+ createInfo.surface = demo->window;
+
+ err = vkCreateWaylandSurfaceKHR(demo->inst, &createInfo, NULL,
+ &demo->surface);
+#elif defined(VK_USE_PLATFORM_MIR_KHR)
+#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
+ VkAndroidSurfaceCreateInfoKHR createInfo;
+ createInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
+ createInfo.pNext = NULL;
+ createInfo.flags = 0;
+ createInfo.window = (ANativeWindow*)(demo->window);
+
+ err = vkCreateAndroidSurfaceKHR(demo->inst, &createInfo, NULL, &demo->surface);
+#elif defined(VK_USE_PLATFORM_XLIB_KHR)
+ VkXlibSurfaceCreateInfoKHR createInfo;
+ createInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR;
+ createInfo.pNext = NULL;
+ createInfo.flags = 0;
+ createInfo.dpy = demo->display;
+ createInfo.window = demo->xlib_window;
+
+ err = vkCreateXlibSurfaceKHR(demo->inst, &createInfo, NULL,
+ &demo->surface);
+#elif defined(VK_USE_PLATFORM_XCB_KHR)
+ VkXcbSurfaceCreateInfoKHR createInfo;
+ createInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR;
+ createInfo.pNext = NULL;
+ createInfo.flags = 0;
+ createInfo.connection = demo->connection;
+ createInfo.window = demo->xcb_window;
+
+ err = vkCreateXcbSurfaceKHR(demo->inst, &createInfo, NULL, &demo->surface);
+#elif defined(VK_USE_PLATFORM_DISPLAY_KHR)
+ err = demo_create_display_surface(demo);
+#elif defined(VK_USE_PLATFORM_IOS_MVK)
+ VkIOSSurfaceCreateInfoMVK surface;
+ surface.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK;
+ surface.pNext = NULL;
+ surface.flags = 0;
+ surface.pView = demo->window;
+
+ err = vkCreateIOSSurfaceMVK(demo->inst, &surface, NULL, &demo->surface);
+#elif defined(VK_USE_PLATFORM_MACOS_MVK)
+ VkMacOSSurfaceCreateInfoMVK surface;
+ surface.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
+ surface.pNext = NULL;
+ surface.flags = 0;
+ surface.pView = demo->window;
+
+ err = vkCreateMacOSSurfaceMVK(demo->inst, &surface, NULL, &demo->surface);
+#endif
+ assert(!err);
+
+ // Iterate over each queue to learn whether it supports presenting:
+ VkBool32 *supportsPresent =
+ (VkBool32 *)malloc(demo->queue_family_count * sizeof(VkBool32));
+ for (uint32_t i = 0; i < demo->queue_family_count; i++) {
+ demo->fpGetPhysicalDeviceSurfaceSupportKHR(demo->gpu, i, demo->surface,
+ &supportsPresent[i]);
+ }
+
+ // Search for a graphics and a present queue in the array of queue
+ // families, try to find one that supports both
+ uint32_t graphicsQueueFamilyIndex = UINT32_MAX;
+ uint32_t presentQueueFamilyIndex = UINT32_MAX;
+ for (uint32_t i = 0; i < demo->queue_family_count; i++) {
+ if ((demo->queue_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) {
+ if (graphicsQueueFamilyIndex == UINT32_MAX) {
+ graphicsQueueFamilyIndex = i;
+ }
+
+ if (supportsPresent[i] == VK_TRUE) {
+ graphicsQueueFamilyIndex = i;
+ presentQueueFamilyIndex = i;
+ break;
+ }
+ }
+ }
+
+ if (presentQueueFamilyIndex == UINT32_MAX) {
+ // If didn't find a queue that supports both graphics and present, then
+ // find a separate present queue.
+ for (uint32_t i = 0; i < demo->queue_family_count; ++i) {
+ if (supportsPresent[i] == VK_TRUE) {
+ presentQueueFamilyIndex = i;
+ break;
+ }
+ }
+ }
+
+ // Generate error if could not find both a graphics and a present queue
+ if (graphicsQueueFamilyIndex == UINT32_MAX ||
+ presentQueueFamilyIndex == UINT32_MAX) {
+ ERR_EXIT("Could not find both graphics and present queues\n",
+ "Swapchain Initialization Failure");
+ }
+
+ demo->graphics_queue_family_index = graphicsQueueFamilyIndex;
+ demo->present_queue_family_index = presentQueueFamilyIndex;
+ demo->separate_present_queue =
+ (demo->graphics_queue_family_index != demo->present_queue_family_index);
+ free(supportsPresent);
+
+ demo_create_device(demo);
+
+ GET_DEVICE_PROC_ADDR(demo->device, CreateSwapchainKHR);
+ GET_DEVICE_PROC_ADDR(demo->device, DestroySwapchainKHR);
+ GET_DEVICE_PROC_ADDR(demo->device, GetSwapchainImagesKHR);
+ GET_DEVICE_PROC_ADDR(demo->device, AcquireNextImageKHR);
+ GET_DEVICE_PROC_ADDR(demo->device, QueuePresentKHR);
+ if (demo->VK_GOOGLE_display_timing_enabled) {
+ GET_DEVICE_PROC_ADDR(demo->device, GetRefreshCycleDurationGOOGLE);
+ GET_DEVICE_PROC_ADDR(demo->device, GetPastPresentationTimingGOOGLE);
+ }
+
+ vkGetDeviceQueue(demo->device, demo->graphics_queue_family_index, 0,
+ &demo->graphics_queue);
+
+ if (!demo->separate_present_queue) {
+ demo->present_queue = demo->graphics_queue;
+ } else {
+ vkGetDeviceQueue(demo->device, demo->present_queue_family_index, 0,
+ &demo->present_queue);
+ }
+
+ // Get the list of VkFormat's that are supported:
+ uint32_t formatCount;
+ err = demo->fpGetPhysicalDeviceSurfaceFormatsKHR(demo->gpu, demo->surface,
+ &formatCount, NULL);
+ assert(!err);
+ VkSurfaceFormatKHR *surfFormats =
+ (VkSurfaceFormatKHR *)malloc(formatCount * sizeof(VkSurfaceFormatKHR));
+ err = demo->fpGetPhysicalDeviceSurfaceFormatsKHR(demo->gpu, demo->surface,
+ &formatCount, surfFormats);
+ assert(!err);
+ // If the format list includes just one entry of VK_FORMAT_UNDEFINED,
+ // the surface has no preferred format. Otherwise, at least one
+ // supported format will be returned.
+ if (formatCount == 1 && surfFormats[0].format == VK_FORMAT_UNDEFINED) {
+ demo->format = VK_FORMAT_B8G8R8A8_UNORM;
+ } else {
+ assert(formatCount >= 1);
+ demo->format = surfFormats[0].format;
+ }
+ demo->color_space = surfFormats[0].colorSpace;
+
+ demo->quit = false;
+ demo->curFrame = 0;
+
+ // Create semaphores to synchronize acquiring presentable buffers before
+ // rendering and waiting for drawing to be complete before presenting
+ VkSemaphoreCreateInfo semaphoreCreateInfo = {
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
+ .pNext = NULL,
+ .flags = 0,
+ };
+
+ // Create fences that we can use to throttle if we get too far
+ // ahead of the image presents
+ VkFenceCreateInfo fence_ci = {
+ .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
+ .pNext = NULL,
+ .flags = VK_FENCE_CREATE_SIGNALED_BIT
+ };
+ for (uint32_t i = 0; i < FRAME_LAG; i++) {
+ err = vkCreateFence(demo->device, &fence_ci, NULL, &demo->fences[i]);
+ assert(!err);
+
+ err = vkCreateSemaphore(demo->device, &semaphoreCreateInfo, NULL,
+ &demo->image_acquired_semaphores[i]);
+ assert(!err);
+
+ err = vkCreateSemaphore(demo->device, &semaphoreCreateInfo, NULL,
+ &demo->draw_complete_semaphores[i]);
+ assert(!err);
+
+ if (demo->separate_present_queue) {
+ err = vkCreateSemaphore(demo->device, &semaphoreCreateInfo, NULL,
+ &demo->image_ownership_semaphores[i]);
+ assert(!err);
+ }
+ }
+ demo->frame_index = 0;
+
+ // Get Memory information and properties
+ vkGetPhysicalDeviceMemoryProperties(demo->gpu, &demo->memory_properties);
+}
+
+#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
+static void registry_handle_global(void *data, struct wl_registry *registry,
+ uint32_t name, const char *interface,
+ uint32_t version UNUSED) {
+ struct demo *demo = data;
+ if (strcmp(interface, "wl_compositor") == 0) {
+ demo->compositor =
+ wl_registry_bind(registry, name, &wl_compositor_interface, 3);
+ /* Todo: When xdg_shell protocol has stablized, we should move wl_shell
+ * tp xdg_shell */
+ } else if (strcmp(interface, "wl_shell") == 0) {
+ demo->shell = wl_registry_bind(registry, name, &wl_shell_interface, 1);
+ }
+}
+
+static void registry_handle_global_remove(void *data UNUSED,
+ struct wl_registry *registry UNUSED,
+ uint32_t name UNUSED) {}
+
+static const struct wl_registry_listener registry_listener = {
+ registry_handle_global, registry_handle_global_remove};
+#elif defined(VK_USE_PLATFORM_MIR_KHR)
+#endif
+
+static void demo_init_connection(struct demo *demo) {
+#if defined(VK_USE_PLATFORM_XCB_KHR)
+ const xcb_setup_t *setup;
+ xcb_screen_iterator_t iter;
+ int scr;
+
+ demo->connection = xcb_connect(NULL, &scr);
+ if (xcb_connection_has_error(demo->connection) > 0) {
+ printf("Cannot find a compatible Vulkan installable client driver "
+ "(ICD).\nExiting ...\n");
+ fflush(stdout);
+ exit(1);
+ }
+
+ setup = xcb_get_setup(demo->connection);
+ iter = xcb_setup_roots_iterator(setup);
+ while (scr-- > 0)
+ xcb_screen_next(&iter);
+
+ demo->screen = iter.data;
+#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
+ demo->display = wl_display_connect(NULL);
+
+ if (demo->display == NULL) {
+ printf("Cannot find a compatible Vulkan installable client driver "
+ "(ICD).\nExiting ...\n");
+ fflush(stdout);
+ exit(1);
+ }
+
+ demo->registry = wl_display_get_registry(demo->display);
+ wl_registry_add_listener(demo->registry, &registry_listener, demo);
+ wl_display_dispatch(demo->display);
+#elif defined(VK_USE_PLATFORM_MIR_KHR)
+#endif
+}
+
+static void demo_init(struct demo *demo, int argc, char **argv) {
+ vec3 eye = {0.0f, 3.0f, 5.0f};
+ vec3 origin = {0, 0, 0};
+ vec3 up = {0.0f, 1.0f, 0.0};
+
+ memset(demo, 0, sizeof(*demo));
+ demo->presentMode = VK_PRESENT_MODE_FIFO_KHR;
+ demo->frameCount = INT32_MAX;
+
+ for (int i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "--use_staging") == 0) {
+ demo->use_staging_buffer = true;
+ continue;
+ }
+ if ((strcmp(argv[i], "--present_mode") == 0) &&
+ (i < argc - 1)) {
+ demo->presentMode = atoi(argv[i+1]);
+ i++;
+ continue;
+ }
+ if (strcmp(argv[i], "--break") == 0) {
+ demo->use_break = true;
+ continue;
+ }
+ if (strcmp(argv[i], "--validate") == 0) {
+ demo->validate = true;
+ continue;
+ }
+ if (strcmp(argv[i], "--validate-checks-disabled") == 0) {
+ demo->validate = true;
+ demo->validate_checks_disabled = true;
+ continue;
+ }
+ if (strcmp(argv[i], "--xlib") == 0) {
+ fprintf(stderr, "--xlib is deprecated and no longer does anything");
+ continue;
+ }
+ if (strcmp(argv[i], "--c") == 0 && demo->frameCount == INT32_MAX &&
+ i < argc - 1 && sscanf(argv[i + 1], "%d", &demo->frameCount) == 1 &&
+ demo->frameCount >= 0) {
+ i++;
+ continue;
+ }
+ if (strcmp(argv[i], "--suppress_popups") == 0) {
+ demo->suppress_popups = true;
+ continue;
+ }
+ if (strcmp(argv[i], "--display_timing") == 0) {
+ demo->VK_GOOGLE_display_timing_enabled = true;
+ continue;
+ }
+ if (strcmp(argv[i], "--incremental_present") == 0) {
+ demo->VK_KHR_incremental_present_enabled = true;
+ continue;
+ }
+
+#if defined(ANDROID)
+ ERR_EXIT("Usage: cube [--validate]\n", "Usage");
+#else
+ fprintf(stderr, "Usage:\n %s [--use_staging] [--validate] [--validate-checks-disabled] [--break] "
+ "[--c <framecount>] [--suppress_popups] [--incremental_present] [--display_timing] [--present_mode <present mode enum>]\n"
+ "VK_PRESENT_MODE_IMMEDIATE_KHR = %d\n"
+ "VK_PRESENT_MODE_MAILBOX_KHR = %d\n"
+ "VK_PRESENT_MODE_FIFO_KHR = %d\n"
+ "VK_PRESENT_MODE_FIFO_RELAXED_KHR = %d\n",
+ APP_SHORT_NAME, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_MAILBOX_KHR,
+ VK_PRESENT_MODE_FIFO_KHR, VK_PRESENT_MODE_FIFO_RELAXED_KHR);
+ fflush(stderr);
+ exit(1);
+#endif
+ }
+
+ demo_init_connection(demo);
+
+ demo_init_vk(demo);
+
+ demo->width = 500;
+ demo->height = 500;
+
+ demo->spin_angle = 4.0f;
+ demo->spin_increment = 0.2f;
+ demo->pause = false;
+
+ mat4x4_perspective(demo->projection_matrix, (float)degreesToRadians(45.0f),
+ 1.0f, 0.1f, 100.0f);
+ mat4x4_look_at(demo->view_matrix, eye, origin, up);
+ mat4x4_identity(demo->model_matrix);
+
+ demo->projection_matrix[1][1]*=-1; //Flip projection matrix from GL to Vulkan orientation.
+}
+
+#if defined(VK_USE_PLATFORM_WIN32_KHR)
+// Include header required for parsing the command line options.
+#include <shellapi.h>
+
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLine,
+ int nCmdShow) {
+ MSG msg; // message
+ bool done; // flag saying when app is complete
+ int argc;
+ char **argv;
+
+ // Ensure wParam is initialized.
+ msg.wParam = 0;
+
+ // Use the CommandLine functions to get the command line arguments.
+ // Unfortunately, Microsoft outputs
+ // this information as wide characters for Unicode, and we simply want the
+ // Ascii version to be compatible
+ // with the non-Windows side. So, we have to convert the information to
+ // Ascii character strings.
+ LPWSTR *commandLineArgs = CommandLineToArgvW(GetCommandLineW(), &argc);
+ if (NULL == commandLineArgs) {
+ argc = 0;
+ }
+
+ if (argc > 0) {
+ argv = (char **)malloc(sizeof(char *) * argc);
+ if (argv == NULL) {
+ argc = 0;
+ } else {
+ for (int iii = 0; iii < argc; iii++) {
+ size_t wideCharLen = wcslen(commandLineArgs[iii]);
+ size_t numConverted = 0;
+
+ argv[iii] = (char *)malloc(sizeof(char) * (wideCharLen + 1));
+ if (argv[iii] != NULL) {
+ wcstombs_s(&numConverted, argv[iii], wideCharLen + 1,
+ commandLineArgs[iii], wideCharLen + 1);
+ }
+ }
+ }
+ } else {
+ argv = NULL;
+ }
+
+ demo_init(&demo, argc, argv);
+
+ // Free up the items we had to allocate for the command line arguments.
+ if (argc > 0 && argv != NULL) {
+ for (int iii = 0; iii < argc; iii++) {
+ if (argv[iii] != NULL) {
+ free(argv[iii]);
+ }
+ }
+ free(argv);
+ }
+
+ demo.connection = hInstance;
+ strncpy(demo.name, "cube", APP_NAME_STR_LEN);
+ demo_create_window(&demo);
+ demo_init_vk_swapchain(&demo);
+
+ demo_prepare(&demo);
+
+ done = false; // initialize loop condition variable
+
+ // main message loop
+ while (!done) {
+ PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
+ if (msg.message == WM_QUIT) // check for a quit message
+ {
+ done = true; // if found, quit app
+ } else {
+ /* Translate and dispatch to event queue*/
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ RedrawWindow(demo.window, NULL, NULL, RDW_INTERNALPAINT);
+ }
+
+ demo_cleanup(&demo);
+
+ return (int)msg.wParam;
+}
+
+#elif defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK)
+static void demo_main(struct demo *demo, void* view) {
+ const char* argv[] = { "CubeSample" };
+ int argc = sizeof(argv) / sizeof(char*);
+
+ demo_init(demo, argc, (char**)argv);
+ demo->window = view;
+ demo_init_vk_swapchain(demo);
+ demo_prepare(demo);
+ demo->spin_angle = 0.4f;
+}
+
+static void demo_update_and_draw(struct demo *demo) {
+ // Wait for work to finish before updating MVP.
+ vkDeviceWaitIdle(demo->device);
+ demo_update_data_buffer(demo);
+
+ demo_draw(demo);
+}
+
+#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
+#include <android/log.h>
+#include <android_native_app_glue.h>
+#include "android_util.h"
+
+static bool initialized = false;
+static bool active = false;
+struct demo demo;
+
+static int32_t processInput(struct android_app* app, AInputEvent* event) {
+ return 0;
+}
+
+static void processCommand(struct android_app* app, int32_t cmd) {
+ switch(cmd) {
+ case APP_CMD_INIT_WINDOW: {
+ if (app->window) {
+ // We're getting a new window. If the app is starting up, we
+ // need to initialize. If the app has already been
+ // initialized, that means that we lost our previous window,
+ // which means that we have a lot of work to do. At a minimum,
+ // we need to destroy the swapchain and surface associated with
+ // the old window, and create a new surface and swapchain.
+ // However, since there are a lot of other objects/state that
+ // is tied to the swapchain, it's easiest to simply cleanup and
+ // start over (i.e. use a brute-force approach of re-starting
+ // the app)
+ if (demo.prepared) {
+ demo_cleanup(&demo);
+ }
+
+ // Parse Intents into argc, argv
+ // Use the following key to send arguments, i.e.
+ // --es args "--validate"
+ const char key[] = "args";
+ char* appTag = (char*) APP_SHORT_NAME;
+ int argc = 0;
+ char** argv = get_args(app, key, appTag, &argc);
+
+ __android_log_print(ANDROID_LOG_INFO, appTag, "argc = %i", argc);
+ for (int i = 0; i < argc; i++)
+ __android_log_print(ANDROID_LOG_INFO, appTag, "argv[%i] = %s", i, argv[i]);
+
+ demo_init(&demo, argc, argv);
+
+ // Free the argv malloc'd by get_args
+ for (int i = 0; i < argc; i++)
+ free(argv[i]);
+
+ demo.window = (void*)app->window;
+ demo_init_vk_swapchain(&demo);
+ demo_prepare(&demo);
+ initialized = true;
+ }
+ break;
+ }
+ case APP_CMD_GAINED_FOCUS: {
+ active = true;
+ break;
+ }
+ case APP_CMD_LOST_FOCUS: {
+ active = false;
+ break;
+ }
+ }
+}
+
+void android_main(struct android_app *app)
+{
+ app_dummy();
+
+#ifdef ANDROID
+ int vulkanSupport = InitVulkan();
+ if (vulkanSupport == 0)
+ return;
+#endif
+
+ demo.prepared = false;
+
+ app->onAppCmd = processCommand;
+ app->onInputEvent = processInput;
+
+ while(1) {
+ int events;
+ struct android_poll_source* source;
+ while (ALooper_pollAll(active ? 0 : -1, NULL, &events, (void**)&source) >= 0) {
+ if (source) {
+ source->process(app, source);
+ }
+
+ if (app->destroyRequested != 0) {
+ demo_cleanup(&demo);
+ return;
+ }
+ }
+ if (initialized && active) {
+ demo_run(&demo);
+ }
+ }
+
+}
+#else
+int main(int argc, char **argv) {
+ struct demo demo;
+
+ demo_init(&demo, argc, argv);
+#if defined(VK_USE_PLATFORM_XCB_KHR)
+ demo_create_xcb_window(&demo);
+#elif defined(VK_USE_PLATFORM_XLIB_KHR)
+ demo_create_xlib_window(&demo);
+#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
+ demo_create_window(&demo);
+#elif defined(VK_USE_PLATFORM_MIR_KHR)
+#endif
+
+ demo_init_vk_swapchain(&demo);
+
+ demo_prepare(&demo);
+
+#if defined(VK_USE_PLATFORM_XCB_KHR)
+ demo_run_xcb(&demo);
+#elif defined(VK_USE_PLATFORM_XLIB_KHR)
+ demo_run_xlib(&demo);
+#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
+ demo_run(&demo);
+#elif defined(VK_USE_PLATFORM_MIR_KHR)
+#elif defined(VK_USE_PLATFORM_DISPLAY_KHR)
+ demo_run_display(&demo);
+#endif
+
+ demo_cleanup(&demo);
+
+ return validation_error;
+}
+#endif
diff --git a/cube.frag b/cube.frag
new file mode 100644
index 0000000..70ba93e
--- /dev/null
+++ b/cube.frag
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2015-2016 The Khronos Group Inc.
+ * Copyright (c) 2015-2016 Valve Corporation
+ * Copyright (c) 2015-2016 LunarG, Inc.
+ *
+ * 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.
+ */
+/*
+ * Fragment shader for cube demo
+ */
+#version 400
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_ARB_shading_language_420pack : enable
+layout (binding = 1) uniform sampler2D tex;
+
+layout (location = 0) in vec4 texcoord;
+layout (location = 0) out vec4 uFragColor;
+void main() {
+ uFragColor = texture(tex, texcoord.xy);
+}
diff --git a/cube.vert b/cube.vert
new file mode 100644
index 0000000..1e67f75
--- /dev/null
+++ b/cube.vert
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2015-2016 The Khronos Group Inc.
+ * Copyright (c) 2015-2016 Valve Corporation
+ * Copyright (c) 2015-2016 LunarG, Inc.
+ *
+ * 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.
+ */
+/*
+ * Vertex shader used by Cube demo.
+ */
+#version 400
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_ARB_shading_language_420pack : enable
+layout(std140, binding = 0) uniform buf {
+ mat4 MVP;
+ vec4 position[12*3];
+ vec4 attr[12*3];
+} ubuf;
+
+layout (location = 0) out vec4 texcoord;
+
+out gl_PerVertex {
+ vec4 gl_Position;
+};
+
+void main()
+{
+ texcoord = ubuf.attr[gl_VertexIndex];
+ gl_Position = ubuf.MVP * ubuf.position[gl_VertexIndex];
+}
diff --git a/gettime.h b/gettime.h
new file mode 100644
index 0000000..a4265cd
--- /dev/null
+++ b/gettime.h
@@ -0,0 +1,74 @@
+/**************************************************************************
+ *
+ * Copyright 2014, 2017 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.
+ *
+ * Ported from drawElements Utility Library (Google, Inc.)
+ * Port done by: Ian Elliott <ianelliott@google.com>
+ **************************************************************************/
+
+#include <time.h>
+#include <assert.h>
+#include <vulkan/vk_platform.h>
+
+#if defined(_WIN32)
+
+#include <windows.h>
+
+#elif defined(__unix__) || defined(__linux) || defined(__linux__) || defined(__ANDROID__) || defined(__EPOC32__) || defined(__QNX__)
+
+#include <time.h>
+
+#elif defined(__APPLE__)
+
+#include <sys/time.h>
+
+#endif
+
+uint64_t getTimeInNanoseconds(void) {
+#if defined(_WIN32)
+ LARGE_INTEGER freq;
+ LARGE_INTEGER count;
+ QueryPerformanceCounter(&count);
+ QueryPerformanceFrequency(&freq);
+ assert(freq.LowPart != 0 || freq.HighPart != 0);
+
+ if (count.QuadPart < MAXLONGLONG / 1000000) {
+ assert(freq.QuadPart != 0);
+ return count.QuadPart * 1000000 / freq.QuadPart;
+ } else {
+ assert(freq.QuadPart >= 1000000);
+ return count.QuadPart / (freq.QuadPart / 1000000);
+ }
+
+#elif defined(__unix__) || defined(__linux) || defined(__linux__) || defined(__ANDROID__) || defined(__QNX__)
+ struct timespec currTime;
+ clock_gettime(CLOCK_MONOTONIC, &currTime);
+ return (uint64_t)currTime.tv_sec * 1000000 + ((uint64_t)currTime.tv_nsec / 1000);
+
+#elif defined(__EPOC32__)
+ struct timespec currTime;
+ /* Symbian supports only realtime clock for clock_gettime. */
+ clock_gettime(CLOCK_REALTIME, &currTime);
+ return (uint64_t)currTime.tv_sec * 1000000 + ((uint64_t)currTime.tv_nsec / 1000);
+
+#elif defined(__APPLE__)
+ struct timeval currTime;
+ gettimeofday(&currTime, NULL);
+ return (uint64_t)currTime.tv_sec * 1000000 + (uint64_t)currTime.tv_usec;
+
+#else
+#error "Not implemented for target OS"
+#endif
+}
diff --git a/jesse.ppm b/jesse.ppm
new file mode 100644
index 0000000..50a18aa
--- /dev/null
+++ b/jesse.ppm
@@ -0,0 +1,5 @@
+P6
+# CREATOR: GIMP PNM Filter Version 1.1
+256 256
+255
+dkK/{U=\D[A|R:T<ZAaF[CX@Z=}V9{Q7tJ4uM5uN;xUCr[lM:oL7~bugQ~iu_viVk]KcW@`N8\G.XD(S="Q8O5N5Q6M2M3L2H-D+A+@,=-4**#(#*&*'%# %!96#+$:4$ED)D?'B;'C=%C;(?9$FA+-'IYAUjTOpMҔilMwVLiJJhEMdGPkNU{VuvuyɢX[箶靅}rzfo`j_l\ko}ZnXh_nᲳ妨twV]}~~gkK1zU>~YB]F}S<T<Y?_D\CU>V;~V<zR9vM6rL3uO:vVBdnM:mL6{]p_Jud~p^uhUj^JcT>^K2]I,XD'U=#Q6N5O6O5L1N4L3H.D,B,?,=.3))"*%*&*&)'  $ 96".(81"ED(C>%C<'B=#C;(B<(GB.*"TaLXlYRlRhhYq\]w\PmLPhLQkQV_moV[䘰럥לÖxlujsmul}WhZd|t력✏Ǎ[a}}~~elR4zV>zT=|V?|R<U=X>Y@V>S<V>T<{Q9uM6qL3vQ>vUBolK:jK4x]v`Oq`ylYteSo_NdV?]I1\I,YE'T>!P7Q7M4K1I.M3K3G-D+B+>,<-4)& *&,&,&.+#&!97"/)@9(EA'D=$D<&B>$A:$D>(LI1'!TaL]mYTjNzccvs{qRwNSnPQpPV\fjquګdaɈ穴䧮ܟɐϪfw[kYcʐz֕el|bjH-xS;yU;xS:{S:{Q8~U<W=S;R:V=}T;zQ9uL7uP7xT?zUD|f{mK:lH3sWs^Ln^ujTpaNiYIcW@`P6]I/YE'T>"O6N5N5M3M2M3L1H,D,A+?,=.5*("'$($+&(&"!' >;(.&;3!HC(F?'E>&C=#C;$D?&KH/)&SdKXiTYrS^_}ҐGsDXsSXwTVS^\ghɛ`]ѕ槧ڙ箪ٝ͗Η٬ݼ\rar[fboaj|pwy|ckH-uO8uQ9tQ5zS:wO5}V:X;U:}Q8U<{S9zS9zQ:wT9yT=zUDzcnsM=oF6u[mUB}i[ueRo_MhWDaR:_N4[G,YF(Q<Q9P8K2K0L0M1K/H,F,B,?,;-4))#)$+&+%*("% A>+2(D;)HC)G@)D>$D>!D=%E@'IG-(# [nSVgSUtPԘ]f]gMSW|R^{X_|Za[c]b^tqӒޤdgڝŶl}n}ku~q|~}ddD'pM1rN5sO4vQ6xQ7}V:Z=W<S:|S8~U;|R:{R;zT:{S>xSBkqpF:mF7w^qVH|fYueSn^LfU?`P6\J/ZF)VC#Q;Q7O6N3L1J.F+L0H+C*A*=,;.3++%+%+&+%(&#%!?>(-&A8&EA&D<%F=&D>$E=&G?'EC+"" _sZQePheanVuZSvQ\Ybab`jjsonh}t{xutޭwyןs{w~~~~~gdD(qM2uR7vR9xT8{T:~V;}V9X>U<T<V=~U=|R;{S9}VAzQBoroJ<nH6s[rYIxdVn_Ml[IgVC_O6]I0ZF*VA$R9Q8M5O5M1J/I/I/H-F-?)>,:-5,&'",&,%+) % $;9%2*D;)HB(H@'D<%E?%D=&GA(II/!`r]TdSgmepV\ZxXW~Sa]mmgjrr|uiexqy~x˙z{zǎ޲hu}}~~yaqN7sL6uQ8xU;zU;{V<~V<Z@Y@W>~S9W=X>|S:yS9{U<WFjmpN;lL5z\mXCveTncOfWCeT@`O6[I/ZF*VA$U=!N6N5M3L0K0J0I.G+D+B+@.>.6*+#+%)#+%(&$#@=*0%@7&IC)G?(F>'C=$D>&E@(JH2!#dw_R`P[u]^`ZvW]zZ^\kh|rq{z}v{sia{rnӣ{֯ˢέewϫÛhzp~~~z|y`pM5tN7wR;yU<zV;zS9}U;Y>\BX?Z>Y<W<{S8yT6zV:[GhjjH2lM8x]pXE{hYobNdW?bN6]I/\G/[G,XB%Q9Q:N5O4L0J/J0J0L0G/B,B/A/7**#*%)$)&)' $ # ED-0(C:(EA&E>&C<%C=$B=%EA'NJ5!! bx\MaLTlTbdX}Y\~Xffrq͛˓Бʁxtklީ|ЦǧҀԧb{bx~~ycmJ3xS<wR;xS<|W={S9zR7Y;Z>W<Z>X<}U9zR8|U8|V<~XElfhC2pM:v_pSD}iZl\GdU<aM5]I0XA(YD(V?#R9P7L4O4K/J.J/I.G+D-B,B/@/6*.'+$,&($&$!&!#B?'-$E>+FB'F>'E>&D@'B>'FC(JI3 !ex^N^HYtXml]}Yfbhfzrˆܥާޭ̺쩘̏qoyy׭ȤҪސţxskvx_oL5xS;vQ9zV<{W<xQ7xO5{S6~U9~U9X=~U9U:}S:|R9}T<YGl{bpH9nI6{`oVCudSk\IcU?aS:]M3YC+VA&V>"S9O5P7O5J/I.G.G.F.B,A,@.?.4,($($*%&#)'&!(!?<&,"F>+IC*G@(F>(C?&C>(DA*FH2 cxaLYF`x]nnb{^injnؒΏԙֹ谞җpqؽв۸slqԧӼw\pM7vP:vQ9yV<|X=vN4xP3xO1~U8W;~V:W<|S8W=}T:X>ZF}h{`pL8lG2|_hQ;udRi\FcV<`P7]L2XD*VB&V>"R9R9O7M5K1F-E,E.C*B,@/?.?.2)&!*&*&)%*(%!%BA)+"E=(ID(F?'E?'D@'D?*EC,BB, %a~fHXF`x_emeofpjp|~ÒÖÑƔtyԻɯͱ衒r[qL8xP>yS>{U>zS:yQ7tL0zR3|T7~V;X;~W:}U9|R9|U;Y?~\F}g~boK8gA.gdK6uaQl]FdU;`L4\G/XC*WA&U=$Q8Q7N5K2I.F-E,F-C,B-?-@.>/6,+$*%+&-''$ $&FC+)"F@+JF+F@'D?&D@'C?)BC)EH1! b}cGZG_x_qyąٕɎ͕ϕ͙ܨ||jtgqky}q{愲|կnrYqL5tP9zT@{U>yQ9xP6vM1xO2{Q6~T;~T:}S9|S9{R9}U;}U=^K|bxYlF1i>.t^nO?u`Rm^JeU>_J2YD,YB*YB'U=#R:O6O6M5I0G.E-E-C.A-@.?,?17,,%)$-'.(*(!% &!CA+-#G?*KF-E=&E?&C>&C>)BB*DF0 %b|bIZHjeϖ˅tvэߚ棤ߙ梛ٗٚŌmv}zxẅm~Р~rYoI3rM6yU>yS=yQ8wQ6vK2yP5{Q7}S8}S9}S:{R8zP7{S9}U=^Mv^sTnH3gB/~fPfL;p]Mk]IdT=\L1[I/XC*VA%T=!Q9P8O8L4H/G/E.B-A+@,>->.?06,*$+%.(+%+)"("&A@)+"IA,KD+E='C>%C>&D>*CC+FJ3 'd}gEYGfc֘Âʋ˄|}lqstѷוĘ־挺Ĵɹzu~}pWoK6tO9wT;wS;wP7xR8yQ7{Q8}S:}S:}S:|R9|R9xP7yQ8zQ:[Jq\jRoH5b>+sX_I3l[IgXCaQ8\I/ZG,YD)V@$T< P9O7O8L4F-F.E1D/?*=+>-:*=/3**$-'/(-(+)%#'"(!CB+.'F?)HC'F@&EA&DA&D>(BB(AE-"'c}dGYHbe~oumuirljsmџȧǦƦÿ١nѲ鍮}nWlJ3sN8vR<vR:wP9{S;|T;~T;}S:U<U<U<zQ8vP7xR7zQ;|WEv]hOlH4eE/qV`I5n\JeW=_O4\H/XD*WB'VA$T< R:N6K3J2G-F/D0C/A,<+<,=.<04*)#+$.(0*,'!&"#DE.)"E>)FA&F?'DA%CA&D@(CC*EH0"'bfG\LWtZbe`dv~ŦãγȲڧďl邰h}{mSkL3rP7tS:vR:yS<yS<}S;X>~V<U=W?~U<|T;vO5vQ6vP8ZIwafOhC0jJ7oXfO=m\LdX@[K1[G.WC*VB'U="T>!O7N6L3I1G.G/C.A-A,<+<+<+;.5(-%+$1*.'*'&!(#>C+& LE0JD*F@(EA%EA%FA*CC*KN6 (doG\Ndc՜ޡ䀳|ێրh|pViG0rO7uS;sO8yP:}U=}T;W?~T;W>~T<W?yO7wM5uM3yQ;[Kyd}`JoI8mL;u\GcL9k\JeVA_M5]I/XE)WB'T>!U@"L5O7L4H1E-F-E.B->)<+;+=+:,7)-$-%/'0)+) *&'!?F-(%MF1LF,IB*FC&FB&GB*DC)JM6',ehDXJtsҺʪ{n˵ۉõܥᔼːr~kTkH3qM7uQ;uN8yP:|S<~S<W?V=V>V>W?|Q9yO7xO7{Q>ZJ{g~`LkF3eH1s_GgQ=eT@cQ<`M4ZG-XD)T?%T?"R= O9O8K3J3H0F-B,@+>*=+;)<,<.6)0&.%0(/()'!'"& ?C+1,KE.LG+KE+FB&FC&EB'FF+CG.,2ceDXEþ׿ͪ|n~~ĜwiQkI4sP9tP:wQ:xP9{R:~U<X>U<V=U;~R:V?zP:xO6}SA\J~h~`KkF5eI1wcJ`I3gV@`O6[G/[F-WC(WA'VA$T?!Q:M5L4K3F.F.A+B->+;):)<+:.5(0%/%-%-&)%# )$!GG0)#LG/KG+ID(HC'HD'IC)FD+HJ1)0bhH_Hܗ˛ڱؽtqͨ׿ȅ~~vhMjD1qL7uQ:wQ<yQ:xP7|S:Z@V=U<U<W=W>yP7zR8~W@\Kzc]IqJ;mL9y`IaI7eT<_O5[I/YE,WC(VB&T?!R=Q:N7K2H0F.D-D.@+=);(<*<-:-4(,"-%,%,&)&" &"$!EH/+&KG-IF(IC(HD'GC&IC+HD,HH1'-hmD^Gؚꮭ㟘֏۠ŭuo~x|}sbIiC.qM6uP;wQ<zR9uN3xN4W>W>T;U<V=T<}Q:|S:~VA\LwaaLrJ8jG3~gOfM9bR8]N1YG*YF(VB'VB%T@!R>P:N8K5H1G.E-D.@+=)<)=+:+:-2%,$,%.%-')&#!&# DI-.*JF-LH*IC)FC&HC'JD,IF/GE0,2jjA]F㢯Ʒ鰃وλڮ۶q{õ}{wѷْx۠~|yzhKeA(pO6tQ7wQ:uM5vN4wK2T:U<U<T;~R9R;~Q:|R:UA[Ku`\GrH7mI5|dKfN9bQ7\J-ZF)XE'WD&UB"SAR>P;M6K5K3F/F.D.A,<(<*;*:+9-2',#-&.'.'&$"% "HL2-'KE/LG*KE+F@&IB(G@*LG1GE1).dh@_D⟎ΑۡswqӍЮ|ňzz䎹ւ埓~~tydIfA(oL4rO6vP8vN5uK2xM4~U:T<V=W>U<U<}R;}S;VA[JoX|X@qI7lH4gNfN7_P4[K,YH*VE%UC#UB!S@S?P;M6L5I1H0H0F0B-?+;);*;,;12(*".&,&.)$"" )# GL3,&MH.LG*LE-JD*GA'KC.ID.HE2+3hq;\@СԚvr̥Փ􈹔ȓͻtuyuyθߗ̝}~zeNgG.lL3rN7uO9tL3uK3{P7T;U<V=V=~T;U<|T;~W=YB]JrW^FqH7lI3kPdO8^O2\M,YH)VB$S>S?T@R>O;N9K5I2G0F0D.B-:&;(:';,7+2(-&.'/).))'#!+$$#EK0("MI.KG*KD+H@(IC*IA-LG4GC1&2gq@]EkoЖۣشӘ~ᚿywދvӖЋɟ|}saKkI0nN2rN6wQ;yP8xP7}R9U<T;T;W>}S:U=~T<zR9ZC^LlT]FuM<sS<nQaN5`Q3\M,YH(VC$R? R?SAP<P<L8J5G2D/F0E0E0A.=+=+<-8-2(-&,'.(.((%# )$%"KN3+$RM6IE)KC,HA)HA+H@-MH2FB0.8'ho?\@ͨع㺏ːّqǽֽ}zzjPgD,oL3rM7xO9yP:yP8zP7|R9~T;}R9~U<~T;|T;|S;|U=]F\InW_HpF6qP8mQcO6aS4_N.ZG(WD$R?S?S?Q=O;N:K4I2G0E,A*A*@+?-?->.9-1'-',&.','$"&#,%'$EG04-MH0HD)JB-GA)HB+G?+MI2C?,2='cj=aB߷թ߬ϑw޼Гªʼ}~~{gNlJ1oK4rM6wP:zQ<yP8xO5yP5{Q8}S;U<}T;}U<}U<{W=}ZC]JqY^HpC3pK5jQgS;`N2]K+]I+YF'TA R>S?R>Q9P9L5I0F.F.E.C,@+A,?+@/;.1&,&.&.%.(%"# *$%#FF02*MI0JF+IB,GB(IC,IC-LH3@;(18fn5aCѫ躒Ɩx»ǸҠw`IiF.pK5rN8wO;yR<yQ9xN6yP5{Q8V<}S:}T;}U<zU;yW;yX?_LpU|[EnA2qL8rYAhS;bN3_L-ZF'ZF'UA"VA!T@Q:N7P9M6J2E.F.F.C-C-B-B.@0>/3%.%/(0'0**&"*#$!MK32*NI1HC)IC,EA&HC-E@*KI3B?).8#cm<dGĪٵߺwoŸݾЄ~}}q^EkH0rM7sM9vP<{S=yO9zO7yO6{Q8~T;U<}S:}T<{T<{W<{X@^JlT}\DmA1lJ3rY@gR:bN2^K+WB#XD$VA"VB!T@R<P9N7M6J3G.G.G.D*A)A+D-B0B23%1'2(0(1))&$(#+(JH3.'KF0HD)HB+GE*FB+FA-IK0FG-.9&kq:\=ŖǒǚѪuoعʼȟ~|}~p[BnJ3qL8uO:wQ<yQ;xO8zQ8yQ7yP7yP7{S9}T;~U=|T=|V=ZD[GjT|ZDqI7mM4rX?iT;`M/`K,YD%WB#WC"T@T@R<Q:O7K4I0H-H.H/E,E-E/F0F2A09,4+1(.(+')%% )$$"LK62*MK5GF-IB.FB)IB-ID0KJ3?>)8D,hq>aGϡꎿyuuȶƭΤ~~|q\AsP8sN:xS?yS=yP:xP8yQ8yQ7yP6xN4yP5|U:V>}U>{T=~XC]KjUvX?lG3eA)nV:fN5bL/^I*[F'XC"W@ XA!U>S;R8O4M1L0K/K0H-H.I0G/J1I2G4>-4)2*0(-)%"%"(#$!NM7.'LJ2IF-IB.GB(HC-HD0IJ1@@*<J2{@cKČؔ倮xÖƕйֹԡvmY<pL5tN9wP;yP<yQ:xP7xN5wO3wO2wN2vM1}U:V=|U<}V?|T>]JhSvZAiG0a@'gO3eP5aL/^I*ZD%WC"U?XA!XA!T;S9P5K0N3N2O7N3O3K1L1K0L6N;!?,;-5+3-0,'$)"*#)&LK4-%KH3GD,HA-HD,HA-HC0FH1<C)>UE>b<뮌ׄěΆ⢔~nV<sK6vN;yQ>yP<yP:wN5vL2wN3uL1uL0wN2}S8{Q8{S;|W=}U>\KgTwU>lG0dB)hP3cN0aL.^I)YB"XB!W@XA!YB WAT=R8S:XBX?V<Y= V:W>!\B&bH0kN8XA"L@ B9?4713/*($!.''$FE//'PK6JF.HB.HD,IB/FA/IL6<A*I`S;_;묿謷{ۭêƿڳ{~ypW=rI2xO;|S>{Q;zP9vL3vL1tK/vM0vM0xO3~U;~T;|S<|S<{S<YHhVyS?lC0b<&jP5gQ6bM1^I*[E$XA X@![C#[C"[C Y@X<X@ `H&\B#bE+ZGeUo]hXocp\hPlY=eO4QA&@763*))&'"+(GE/5,QM9JE0IA0ID.FA.F@1LJ:A@/CVK5Z<ǫ⡯䩿Ϻޕ|Ξv}~}}|wqY:vO6}S@}S={R;{S:wM4wM2wN0tK-vN0zQ4~W:}U;{S:|T;|U<ZFlXvS=mE1b;'oQ:iQ9cO2_L+[F#\E#\E#^D%_E'bI(bD'gC.uQ>YBiPr[}]eghptjbxZycG[E+?5,(#!'%('ED-5.QM6IE.LE1HD.HB0B?/KL<>@/8M=0W9ҺӳyŻֲ߉n|xvpotsvuqu{ssZ:yS8VA~T=|T;|S:wN2wL/sJ,tK-yO2|Q5~S8~T;~T<}U<~U>ZGjVuS>nF2i@.tS>kP9eR3_K+_G&`H&cJ+dG+bD(tR9rI6^Jt^gpjdbbcgfbfimt_JA5".&*%+$((FE/4,PI4IB-JB0HD.FA0EC4GK;AB28E7r/Q9ȏךْͯ׷יżƖû}yssmjfhfimkosxxwxzxrq{r{bE~V@YA[AU=}R7xM0yM.zN1wN0|Q4T9~T9~T;}U<}U<}T?[HiUzWCpH6rO9vWAlO:gQ4bO.`J(dK,gL0kP5wYAfGuZkwrk~dec}ezeq]s\qXq]zcgsZL?-3(-%.%+(DE.4,RJ7G>+HA/FA+FC0BB0DI5=@,2E0iw)N/њӘ{|Âӱ崺ˣַ􎽙ƮԲ“NJ~nkikh{e~dcegdfmqstx}~~uuw{y~{wyhfhc^iotoiNbLW?\DV>X>zO1~T4zQ1~T6X9V8U8}V9~U;|T:~T>ZFjU{XDtJ8sQ:vV?nQ:lS8hT3fP/nP5wT<_KpValuqjedfgf|bz_w\rUoVnZu[~`SE/5+/%2&(%CC,;1"WM:KE/G@.FC-CB.DD1DI49>*5J3cp7W;ÅӅՌӏԚƟխηܼڋϵԺʻgx_x[s[s]x`u[qWv[|`y[|\ahllmmqyunggkjqqursrrnihab_\\]`ips|{zoalQaL`H`D]AV9]=Y9\<Z9X8V7W9~V8}V7U<[FhQ{Y@uP9xS<vT<nR6oT9pU9tV;_Gp\lgf{bgjgfgfgfc}_|^}[sWhVlTyYYK45+-$0%,)DC,6/OI3IE-FB-DC*EB.CC.BG2:A+5M2dl:S;߱⼮ȬǶęƴw|aiRkTmTqWqXqUtWuWtWuWwXxZx_fghjmqtzx|~zxz~|qlc^\_glkiZ]dghcb^Z}ZzXuRzU]_gjpqstktPwTvOpLhIjJmLdEbC\=Z:[<~U8~V9X8}U:]FhOyV>tO6yU<zX>tU;wY>}\CkQbomfddedeeghjidv]t[qYoYhViTuY`V@-&*".$,*CB-4,PH3FA)GC.EC*FC.BD.FN79A*4P2lu:^>ʏٷлӺȏź롕}rYdQgThRlToVsWuXvYuYvYuXw[u[x^{_}aefopnkpz{ywwrqnmokiaY{R|QT]d]YUUY``^]V}U{UzTwP{U]^fhkkllg`]\WxR}PxPsOoNoMlLgGcF\>yS4~W?_HmP]D}YAaF]E_HdJoXkqjge~bdggg{a}cgklku_oXsZjWfVfSxa_VA,&,$-$/-DA-91!OI3GD+FB,ED)EC,DF/GO6:C+5O5u*^1ɵ’·񢕃~{rZcRePjUgUt`v^x`x_z`x]wZwZuYvXvZ{`{^aglonkinrstw|upoprmheiihnhgha\}TzPxMzPV[]XVUZ]]\X}RxP{TyP{R~W\`aefikie_c`fb\~Y]^X|UWiGkKgIjQhOmPfMbKbMdPjSyatplidddbdf}b}c|b|dgmoipZnXjVeUgUw^aU=/%/%-#0,?<'6.QL5FD(GC,DC)FD-AC+GN6;>(=T9jw-`9뫉х͋~oVaNgPjRhUu_u_u_x`x_v\sYu[sXsZv[z`}c{`{`hnqnkg~b`cglw|~xrmhhfjii\^aalcbdZY|RxOxNxNVU]Z]X\_]WUxOxQzSxQ}U[_bdcdfgbbaahhicgedcf~VdZ~ZxZsVoPhNfMfNnW~`oohfihe}d{b~d~d~dw_zbzb{c~ijntamXkXcVdTx`VK53+.$,$-/>?(:3"JI/GE)GD,DD(DC)CG,GM39=&:U6gs6W>͡Î焰Żѽv_^MeQaOjXr\u^rZu]qZnXlUsZt\r[t\z`y`w[y\}_jljfh`{XyVtTxSWeqosvsvpmqomllihjg^Z[adcacXV{RxOvMsK}T\^\c_ac]W}TvNtMxQyQV\abeeidca]adrkjhmhkfehgddarTeHcJcKpVchg~c}cfijhjgw_q]uas^q\s]u_|d{boip[iVbVdTqZcXH0).%+%.0@@*71HG.GE+GC,EE)CB)BE+EM16=#=W<fo@ZB§ѶͪȎ{ܹi^N_ObRgUnZpZp[nYiUkVnWu^u_xar[v^rZsZw[w\caccggayYsUnOpPtP~Yabgolelnsromnnoib[]`f^^a[XV{QxNuLX^\]edgdaY}TyPvMxP}RW\__cega^\]agsqrprprnjjgc{QgF_E^EaIrW}`d~c{`c|b}dhhf}fgfwbvbycs]r\s]q[t^gmmWdU[PfSoYH?*0'.%,%0/C@,82MK2FE*GC,CD'DD-CD,FL206BZEbl@bBܶӮۭҔy}ûҼwaNZG]MeXcSoZiXhWiVpYv_u_v_u\jRrZrYt\sZv]x_y\|_|`|agli{_pSlOkNhKpP|X\bfekmtusqutrmf_]^]]`[_\YTwKuGX``cfdjheaX|QyPyQUX\b`_db^]\afnssromjmke]ZrPeC_A\>_Cu[|aw]z`x`z`{azc{b~e|cgl{e|d}hkl}ir]s]r_r]t_p]lX`R]RnVrgLEC&-'+#*$0.DA+82 KI1DC'EA)BB'EC,DD,FJ128B\@`k=eBܲѰݕɺusݮ}hS[GXF\QeUs^eTbSn[t^u_u`s]oZq\u_w^r[t\x_x_w_z]uXw\y]bkmcsRkKhGiHpOsR{TZ_dinphgquurhi][\_^_``[~TzPyPZadgedblje^X|R{RZ`^edc`][\`glovlffd`b[VvLmHcC_?\?gJ|\c}ay]w\x]y_y_yafg~e|clfybuc}ipvxds]t`s\nZcT_P]N\PmWrhKA? ,'+%*'54@<'73LM2GF)HE-FF*CA*CD*HK258IdEZf@rJݺ˹̴vzm߱~kV[HRCXJaRnZfU_Ov`t_v`xao[vawa|cz`u\uZz]x^x[tVsVuZuWwX}`igboOkKdDgFkJoNrPyVZadgedhltpph_\Z___c^]W}RV`egejeeime`[WW]_aecc_[\\bhlojea^[Y]uNnGfAb@\=]?mN~^b{]}^}]b{`x_ffzdhg|dikii{hkrpvbu_pYiVfTaOZJ_QmWwfM>8)$*#)#:9#84 95!KJ0GF+IE.EE*DC,CE+GK249!=Y<]hMzUxsmlԾϬИɔˈɠ~~nYZJQAQC[MdUcV^Ns]vafp]q_hw`~dx\u[tX}^sYsXvYx\{ax\qWpYx^ficwXiMeGhHgIlLnLwQW\``_djmqpki^X\acfb^\\\deihjgihkhc\Z]^bggda_ZZ^fllog^[Z}YYxUlLfD_=\;\=lM}[~]v\tXxYw\}bhgmpr~imltc}hn~i|hl~fqis^p[lVgR[IXJaSlWsbIB;#*$+$*$9;%=;%;7#KJ1ED)GC-ED+FD/EE0FI3-2A_E`oNZݯ̾uЊ±Ȱ}xnƹ~sa[H~P<P@UH`PbTdUvbzcxco^}fgybw`t[oWsZx^w\x_{aey`v]nVoYv`v`ybihx\kNcF`BdEfGmIwN~S]\^`dkqqok][\bgeea\`ceckjhhjhlje^[_ccehfc^\_demgjc^XX|VxSnMeC^>^=dDpRxXy[uYsWoUoVuZza}ginopqspqjlj~i{d|gzejq^lZgUdT\NSG^RnZgW@ND0*#.&*$10<8":6#LL2EC(GB,FD-DB-EC-GF213D_C\lJ~X߽ܱ׺⃱v{ݲϔʺŲm÷ѵ}}ye\KQ?}O?SE[JcSjZvaydxbxbjhycu^rYqYu\y^x]}dhet\sZoWzbt^t]t^{bjjz]lPcF^@`BdDpLwMVY\]cfnosme_[bfkhd``gigklhikejlla\]adfkee_\ahpoidf_T|TxSoLhHcA]>eHrT}^xZoVoVsWtYoVmSu]xa~iiu`zgimruwo~hze}f{ct`s`s]mXgWcR]MWL\NpYaO<=4"*#+$)#87!74>9%LK1GE+FA+EC)GD/FF,KL447IfFZgLWߪܬs{߷qܭ޲{~}}~{h]LRC|N?RBWI_SiWubu_{e{f{ezdv_x`s[v\x\z_bhf}asZy_|bkw_u]t[{_~`dcz[nPaF^BcCjGrJ}OTXXY`gqrskb]dgfhc``ginlihmidmqsa_`afiihg^Z`lqnjcdaVyRpOgDcA[<dFzZa{^rWmUkRqWx]x[sWpVx]di{fnYp\va}hprm|hydp\t\y`r]oYkUgSbOZJZL_MqY[K6:2")#+$,%65!:6"A<*KI1EC)E@+FC+FA-DB-FF1,1KhMY{fM{]߯Цtr~~m_NTE~OA~P@VF]QgYr_yczdtbxczczczcw^{_|`~aghc{a|aeigw]tZt[z\uYlUv]b{]lP_G]>dDlLrOwNW[[]eoromcXbjkge]cefokfknogkmpe`_acjlhf^]ejnjhgdZXuPmIbB\=[@rT~[yYrWnSmTsWuYsW|`|_v\t\iRxb|glvaiVr_zhls|iwct_fRlWu^o[nYjS_LXHYLeSu`LA,;2 $!*$*$:<&63 =8'KI1HE-F@,F@*FA-B@,BC/*0MnSY|cNvZʗޢsv~~zhS~T@Q@}P?VDcSl[vbybwaxd|f}f|fxa{c~e{cfi}a{a}bhlndsZtZsZrYmUiRoWw]e|]hN^B\=`CjJoM}TZ}V_elmqkgadimnh_`fopggppnljmgd]]cikled^_fkhgdg_YvOnKfF];U6lM|]uVrVpTwWx\}bhcx_eg}fuan[kXvdn|fmZk[xbh~htas]~gp\r^xbo[fU`PXKZPdTzbB>)93# '#*'98%72!;6$JI1GB+F@-DB+EA,CC+HK4-5JnMT{^S\ۨܠڡۧҹἾ}kےɨӿpZVB~Q@~QAZGiWr_vbyav]ye~i}gzd}gighhd}b~bflqhx^y]|_x]tYsXlUjSoVw[d|_mO\:Z6`?hFxP~TXaeikqrjcfknnnddhrlgkäymnfmkba]ajoplbX^agkjbdc_}UqNjJ_>V5`EwWsVgPgPmOzZ~acinlv[emf}fyanYq\fjv`p\t^xczgzfj{fu`s^q\nYkV]N]SiU|kTA44.%"*$*%63!71 93#II0HD-FA,DA)EA-GF1FI3+1SmSUv`Yb߳˽ȿț˨냲ĆkΖŠͻg~]G~RBUE`PhYl[o\p]p]r_vbwc}gljji~bz]cfhmiz^x]cdd_xYoVcLlQrUw[~c{]eCU3X7^?oNwSY`dffqrocffmksqonnplt wlnamhc^adktmg_UYkkkd`faZxRpOdCX9bDsWsXlTeMgOkPwW|[~`cinkgnkjh{at\wbik{e{h}jyf{hkj|gzey_w\kUcT`SlYm[H8-1*&#)#(#>=*;5&>7(IG1HE/HA/EA+FB.GF2JH644QhPZycZ`ٽէŐ}z͉ڵ쎽ɯ桔~p{_G~UD[KaOcTeWgViXmYs_vcxc}hzeva{dyav^v\ejlje|cgkiez\{ZuUlSlQrTrU}adxU[=U5Z9cDmMxS]bbajnkb^`ekppnrnmnsĢyqdclnc_cgovkf_~UYlmiaab_}WtOhE`@W9xYx]qYiRkQlPmQqRyZ|\cighihhpk}f{cv`ycjnoqrlonj|htbdT^P]QaWn`[J75*1+$#($,';9'0)@;,GE0GC/GA/EC-FB/EE/LK8.2TsW[gckȞɺ~շz˾ƭͭШ̲~ylUUDVD]J\LbTfVjWnYpYo\m[jXkXlYo[q\u`ycilnfijomje}a}_xZpUmSpRsUuY~^]hGY:V4]=eCnLyUZ]^bif`Y}W`hlmpÞthgqqnsjdkobahkusghYWYihb_\\|WuRoI_;U5dD|\vZmTgOnTqUvXrTrV}`dgdd~`cchih}d{cwawczfknsqlzfvbhZ_TaTaT\TcYydOI,6/0+'$)%&$@>,2+<6&HG1GC/F@-CA+DA.EG1IL8*4[e_lahڽݙzó퀰̺۬ΦŧƫzcWH~P@TCWG]OaScTcSdTaR\OaSgUmXv^zdxb{ekokijlhg|`}bb{^rWlPgNiPoTqVuXxXyXjJ[7Z7\:eDkJsO}SVZb]\|VuR[flkrqhgr›snŢxofkoc]liqpja[U\be\TSyPrMiEd@\:_ArSy[qWnTpVrVpUuXyXyZtV~]je{^ebx[z`e~c{`~ba~cv_uas^|f}kwcnYmXmZkYiVbS]UfVza551*/*$"*&($C@,2)@9+GE0FA-F@-@>)EB/CD.IL9$2co`l^g۷xȸγ߸~ԗ۱ձدզ˩ϡ̤ҩ٢r_KUE}M?QEWJWMWNWL\O\OaScTjZnYf{cubjnn~i~h}e|ccz^{_f~_y[vXoTgOjOjPlTpUuV|\yYrO_?X6\9b@hGtLuNwPVX~VyRxN|Vcipjkmrvwwžvšqejâsb`kmomb]UzR}S\]}WuMsKnIhF\;\9fFyYxZrWmSdNjQpUrVuYzZ}]}_wY{\hnifhil~ezar[s[oYo[nZlWmYkWkWhWjWhWdUbTaT`VhWpcM6-3,2.$ ($&"A>*3)?7(DC-GC.D?,CA-BA-AD-DI4&6emXvcT}_wλٳ񄷴ݧӦӠٲݯجӤ̛ǨаԼޮԡmYXFVEPCTETGTHWJ[O_TaVjZq`udyevcrc}kl~iuasZw\u^x_{^hf|_c|`w\tWxVqRoTnPpRvX}_bkMY<T5X8]AeGiJtPvMX}QzPtK}S`dmggnå{wÝwqpobdsdaflnn`X|R{R{QT|VtRnMhGeD`BY;hG{Y_pUnQjQhRlRoThNkSy\cfcvXz]fmmknorjXcS^R\ObS_P\N[NZMWLVMYN\SaYbYd\g\q_K=$:11,.,&"($%#=<)/(@9+ED/EA.GA1CB-CB.BF/GJ7(1 eoVq_Jo^ڮԳԮԯĤʩԨӿ۱ѮΟɠʥˏźk]JZITDTGVJYN[R^TbXi[m_p`raq`p]vc|gpYqZjUoXtWv]zceieigeid}[sSpRpQoQqTz\fwXbDU5W5X8^AeDqMsOsPwPuLuK|PYbjc_jsjmnmi`]ri_^jkc\zP{QyOzQzStOqNmJeD^>V7Y7oNf|_pSiMcIdIhNoSqVqWqVz^~de|^w]x\bffhfpshv^jUfQgSgVbS`Q\M^R`TcXf[h\j^l^ZP491822-/,(%)%%";:&0*D?.EB.FB.GA0DC.BA0DH3DF6)4'blTp_Nqg|m״دڴթҸװکϟěġ}kXXIUFTIZQ_WcXi[k[dUiYl[m\jXmZn[}aOz^GdNjVrZ{`fookii~cjiy^tXsUpTlQoQsV~aj_hG[9~U2V3\=gFqMlJmJsPuNxMqJ{Wce^{Sqqggjkg[]dh~VxQfh`|VtPsOtRoNqOpLmLsOlG`<[9`=rQhavTkNaGgJiNlPtWz\|^{]z_~chav\tZ{_{ay_|ax]{aegy`w`oYbOvN@zMAVL[RaTdWiZp]s]~fN<,:/4.1..,#!(%($B?,/&E?.EB-E@-E@.DC.CB/DG0EH5*4&[hRoaUmi~ٽsΫާڳ˒ǽȱϡk~YJRGVNYS\U_W_V^T}\N}`O|_O}^O~bQ|_PzYMxSH}\JgTweknqspnlig}by\rXrWtVpTmPqSyZeoadFY=U8U7\>dGhJkKoKrN{Q}PtNyUde_|Tgiedmgfc~YZntJvPahbxQqMpLpNlLmKlJkLnLlI_?[9b@lLmh{\pSlQkNhNiPjQnSu[v[|a}deid{bv^|d{d|ehv_t\ya|fubm]jXeU]O]R`W_U`Wf[dZiZdO6;.7/4,/+/-# &")%B@+0'D>,HD/E@+FA.B?+DA0EG1CE3%,apQja]ok恩wĥ˶ݩԜ̪۬߭۱עjcTPHQF~THSJ~QH}SI~VJwP@pI9kC1wP>zWE^MbRjW{foontsllfdawZvXuVoOjNmMtRvT~]hj{[aFX;~Q5Z>_EgKaCkLlJlI|Q~RtNwPb__wMahagkfbgZ~Un|TlF[gcvNnITsKkIkIjIhKeGfI\@\>`@hIbmcvZrUoShPfMgNbKhNoTu[{`}`bmkhhe{agj}d~figgt_uatcyguefZ_VaX]Vm_[C-7+2+0+0+,) +(&"CC+,'B=)D?)E?*D?+DA,E@/DE2CF6&-!\yoUkh[jiuոәĽxѽ犳ܳڜ۱頓r_|MAxG:vJ:xK=xM?{TDYJwP>tL9pM9zXCaNfSt_~hoopormhb{_}_{]wYuSlLgGeDdGnMzU]d_pO^?Y9U6`BdGdGhEiFfFkJyP}QsPqK_`WuL[_\goedh~X~TcXjF\ijtRoN{UoMhJiIgGfIdFaD^@]=[;`BrTdh|[uUoRkOlNfJ^C]BcHkQsXtZuZbgb]fhmkhgvbze~gmlooiZl\_T^XbVw`K46+3,1+0*-)'$'!*#CB,) F>,HB,F?+E>+C?+DA0CD/EH7%-%arbzsWlnj~ۡyШͺ玸ݤޮߡvXKvD9sA4q@2pB3{RC[NYK^NfSiVr_|ehj}glpnkfbwZvXuYw[pSeJ_DaCaDeJjLzU[~^xUbB[=[<Z=gH`E`ChFgDfFmJxQzRnLnN\\~WoL}T}TXlnegg]]\RrL]dazUkKsPsPeHjJcEdFdEbB`B`?_<X6dExX_yZvVtToQhMeJ`F^E]EdMePhSiSkTnUpUeIpQtWhnnlxdxezfwe|jpsxgf\a\j^|dSD27,3-3,2,/)&#+$+%IF/3(E=)HB*GA)GA+EA+D?-DE0FF7"+!buu~ˌͻƙſӌ폾̜˯٭Тg`wI<tH:sE8m<1UIYNbUkZv`~hmgs]r_~gks}fyaemUcMjRiRcOZD_E^DcFiKmOpQzVzWtTmO]@\=^A_BdG^C_C`@fEgGnKxRqLjIoM{U\[sK}QvK}Tgnbch]b]yRwQX][YsPqOvSjK`BeFcFaDdGbDbDa@[;]?iHrRuVyXwVqThPfP_K`J\GaLgRdPbNbM~aLdO{\GxTCwXFz_JlUwa{g}i|f}gzevc{kt{~nn_bXn_kWJ: 6-70706..)$!*#& FC+*!G?*HA)H@)F?*E@)FA.ED0BC4#+!vZkqʟ°÷Ƽᏺߜќ͟veySBvK:wK<yRB_PaVk[t^|aixcq[n[wclo~iw^uYnTqQ<xY@cHvT?qM8sM5`EjMnKrRtTvUwTqMjIfHbDcCcDdHdEaBdFbBgDfFnKuNoJnKnIyPU{RzMQwMuIdd[bg__XwQsN\~XzUvQvSsQsQqRaEfHcFeIcGgJdHdGfH_AcDkMsRvUuUrTuWpSkSgO_G|\FbN`L}]I}]I`LfPhU\N}ZMrPAoJ=sQ?fReTkYo[xb{e}j~n~nqsgg_tclZR>)C8<4:07/0)("/&+$HC,5(D<&E?$H?(G?)D?(GA.GD0A@1"* ctVggm٫ɵލȾϲ⟓izYFzO@|QBbQgWkYq\q\r\iUjVq\ydjk|es^pZgTtS?lL6pQ9oP7rO9yT>}W@`HmQtTqQqRsUnNlJdDdEeGfHeHdGcD`A_BeCjGfFlKsNlIlHmIqM{P{NU~SvLT`[Z_dd`ZxSrNYtMwPtOxUuQmMnPeGfIfIiKdGeIcHfJgLgJgJhLnOtPvQxV~WZ}\pThO_HyXB~_Kz[F{\G}aKgPjTlVjUhUbR]NyVG}[M~[O]Q_R]Ml_zlry}pletfraKI5=0>1=.8-6,)#0'&LF/2%J?,HA)JB+H>*E>(F=+HB/F?2')#itHcZԿ뒿ªгt}\K~WH^OhXhWiVhUiTmUoYw\c~d}g~gu_kX~_MyYFrR:oP7pP8sS:xY@}\D_HbInSuToOnOmOoOjJgHfGgHhIeHbDcCaBaAdBiGfGiFrNjGiCjEmKyOxN^sK}VZ_uP]`jc\Y{UvQZuOkGoKuPnJlKmLlJhHiKgJcEfIhJlLmMnNpNoMmLrOvRxU~Z`}_vYkQ{]FpL5}\GrR>zZE}_L~bPdQeRiUiTnWhV`NxYF}]K]JaM^NTG]Mr_n~|m~lpYBH6C5;,@0=/6*0(4*'JF-2&H=)JB*KA*KA*F@'G?*IE.HA3+*$htKb[¯{cR_ObScW`QVJ_RlVu]zcd|axar]r[nY^KvT@yXAvU>tS>uU>yXB|[F`KdOjRpUnQlMkLjJlKkKjJhHhHgHfFcDdC_@]>cClJeHlJqLcBgChBkHxOzM[oEYYazSe{Ulf}U}WvRxQ`uRhGjFlJlJlKlKmMiIgIgI`AdFgJgInNoOoMmLnMuQyUvU^_z]qUeMy\CbKbOaNzZG}^NyYJ}\MnM<}\NhYeVucn[}_MiE2vT?`MgVaQ_PjYye{trkS:Q>!@/E4B0E5>09-7*4'NI.0%KA*MC(ND*JB'HA&KC-IG-LF31/$c~lNa\α~hXeUeVcTbTdVkYoZt^q^r\p[gTeUiWbOwVCsP<xXDxYF{[JwVBxT@{WEbMhQpVuXsTrSrQnNmKnKlKjJgGfEcDbBcD`A]@aAmNfIhHlHcA_;d>iE|RQUf@[Xd\hyWnf|S}UVV`xSjHc@fDmLlKkLjKhKdGfI`DcGcFfIjMmNnMmOqOvRyT}Y^}YuTrSpTnSlTmXgTgRiUeTeT|\L{ZL}]Pk\j[o_q_tan[YJ]PfWiYl\zg}xydpX=L1J.R=N7P<"P?$E2E/?,QL-:+OB(QF*RG-M?&MB(QF-PH0LB1,+dnB_LڤдǤ役ָܹص۷ضۧҖsbmYnZm[n[nZo[nYkVbQeRiT}]NbSwVEyUCrR=sT?vUC|]L}bNwXD|ZG_JdNpSvX{]~\}ZwTqPlLlJmJlKjJiGcDdGgIdGcGeGjKfIeD^=a>_:_;eC~U{P{TeC]`diluQonzR|V{U~U[zVoJc@gEmJuPlJmLiIiLgHcDhHfFeElKmLtQtRwSzT{V\]{YxYtUvYv]u^r\mXq[nWkVdRdPlWhVgThVp^s`ra|k|huej[m\rb~kxw{bqV;]D#gN-eM.eM2iQ5bK-V7"T7$K8 UH.I3VE-XK0TD+RD(UL,TI,SJ,OB**+gmKcMivϿھצʟ߾۵ݲ΍ht\w`s_tao]lYjV_Q`RbOxZGz[L{\JxVCvQ@sP>tS>zUCyWEgRbM_JcMiMmQqTz[]`|ZySwQmLoMrMlKlLiJkLgIfIeHiIiJfIcCcA_<Z6b?a@|U}UuPlH\`hqj\po]}Z~WV[vRrLkFiFjGrNiGoMpNiIgFfEgHhGjHjIoNuRwRzUyUzU\_}_y\x[v^oXyapYiVnYxckXiWeT{\KgSmYhUp[zgtbzhrurxd|irxkpSyY=y]=~aCaEaG^EuW=dB-hJ2`G/fM8_F2eT<]N6VF.bV7[O,ZM0UF(OC#>; njFZEȥ˩ܹصױаˊxxbwaubrbn^fV_Q_R_QyWFxUCvUBuTCvS@wUBtS>sN;oI8^MjTsYmTmSpSmRkOvT[\\YW|UzSwQpMmMoMnMhIeHhKhJdGbFeFdD]<[8`;`>xQVoIvQVdjrklnjXZ~SZYvOhFlIhEhHkKlL_>iJgJlLjKkLmLnKmMsQxSzWwS~X}Yabflf}_^vXcLt\zaoZn\kViW~ZJxWEcRl[p^m\wgo[lXxelmljkz[nRiLlLnLjJfJgKaG^B{[@xV?wXC|_Ku]IoWEs^GiS8jV<]M2VI+]R:LE2vi_nV{ġըפݶڰФڳϸ­ïΥɌzu_nZq^m]eVaR_O}]KyTFtO@sP=sP>rO>sO<vR@tT?yYE|XGbMx\x[~_{]{\}_~_vT}W}WZ^\\VqLpNpLkJhKjKjLgJgJfFcFdFa@bA\9]9aAxS[rLlJW\munooi[|T|Q\ZvRgGeDfFhIiKkMcClMjMsRpPnPsPqNoNsOyT{UY\^dggfad}]qVqVy_qXqXs[oZlWeR`OdSmYnYgWiYr^n[fVt^yc|fez\qSvUtSqRnNkKiJkLlNkOeLdJfNeLhOdKfKcEoV8u`EyeKweKn`Gw^}a~Τ٧ު“ɔ֣ஷר̞Ԣ٦ϛ߷Ԧᱽ߶ҫȥµûвҸnpZfSkWeTZM[L{YHxREuL@sN<pL:oH9rL=tP>xRCxSD`OfTcPnWw\fdlge_\[Z\_\\wSqOnLlLjJkKjLgJlNgGfGcFdEgEZ:a?oK}XcuPjIyQ_kuookf]yRrL\}YuSgH]@hIiHeGhJlJpPgJ{WrOoPvSvR{WwSzS|VY\bhbiifey_y]}_ft\~bnUr\t_q\p[{crZtWByYE^MhUp[n[kXlWq\uZrUqSxWxVqRmPhKhLiMlOgJgLiLmOpPpOrQoOiJqPySySyVyXyWaknuzurglmwr{ˑեͦѯϾq|bdNzYE^K^N}WJ|WGyTEtL?rJ<pK9nI7qL;rM<yUD~ZJ~WI\MgVmZs^x_fkjifeaba\WW~W|UsNoLqNmKiFqNkJgHiIdE^B`BmN^?lLvT|W`zUmJxP\ktskecZsNkFXyPwSjHX:\;fDfIgIoNqPoP{XsRmNvQ|T|YzVW~V[_dghljgboTgqhcqYkSvZgou~f~^K{XBwT=uQ<xU@{]G~_LcOfOkSlSgKqRwW}YrOpPkMhJhIeIdHdGlLsPrPvRxSuQzV|Y|WY[Z~W~XZ[^^ab`ccdeggcjpsou~|z{nqWtYBmM4oO6vVAwTCvQBvTCqM<uQAnI9nH8qM:tR>|[HfSnWpXqZv[}`gjihffggea\U~TZ}YsPrPsQpNnKsNhHiHmLbD`Ba@mNfFyTsPzW\}TrMpLZkqmae`YpKqGUxPyTpNV6Z9cCiKiLoOrRrRvStQoMvQ}T}XXWZ]^dhfidheiqjhay]y\ex{gpZbOvW>tP;oJ6qP7tP9xU?y[BzZB~`F~bFcEmLrO[YwRqPkKkIkJiJhIkIqOoOqQtTtUyU|Z}[^\[\~Y}W[ZWYZ^`\^bbbb_efdhciklkgkgglnοȷϵԴnnSpS<hE-c?'iH0oM;nL:oN;rTAsUBsTBsUDwVF}YJcPoZt^v]{_ccijkhgccdd`\}W~T}X^wRySxUqOtPuPoNiIlKhI`BhHqPpOxSxTyV\WwRpJTcmm[cZUnG{OVYwUqTZ:Y9_AlOlNpQsSpOuQpMtSyT~U}VYUX[_dddi_flomlfdkq}zqhr\iVpV}]GpM8lI3rM9qL8uP<uQ<|ZDbGfJhJwQ[]{UtOpMoLpMlJkInLkImKsOpOrQuQvS{W}X\[~XZYYWWX]^`^\cccbecca`fabfedgefgjq²®ұpnSkL1`< b>!dA(iF.jE0hD0lK7hF2qQ>y]KhYj[mYs]ubzfy`ehegifba_\[[Y{UxOwO|XXUzTsPvPvPqPiImLmLfHiKmMpQtPrOyTVUzPmExL_hgxQaVyNtNT\}W\zW\;]=`@kMoQtRwToMwQvRxUX~UY_XY\\`abbbkmledr{{zqmx_a{[kUkToWbL{WCjC.uP8zVA~^H^IdKwS[b~XwRuPvQpNoNnNlLjImKiGnKnKoMrOyS}VzVzT~W[]ZXYc````ec^`dbdc[c`bdghgcagiimq~ǦϭҢ}egJoN1_=bAeF#_:!d<&gE+jD0a6)rP=hPvZ}bbbhhhff``d``\]Y|VzT|VxRwOuNzU\U|UxRySvPrNpMqNjIiLkLkJhJoKpL{UWYrJfBuL_d_zWYVyOmJX\|W]~UfCbBcAjJsQtQ|XvRW[\bX\^\]_Y`fbedhhkop|}wspkiplcd{\rTpQlPmSiOnQrRsRyUY^b_XySvPwRwSuRsPhGiHkJmLlKlLqOvStQtSyTXZ~X}VX[adb^c`\]_`da_^`b_bdefdgflhjms}}~~t}\x`@pP1eD&`?`=_6 a9%gD*gA-lG3}`HrT}]mlhiijgfaY^\Y~Ua]]~W|Y}Z~U}V~Ya\XzUzTtOvRuOqMjGiIeEcEcCjEnKxTZXwNdAvM`bduP|XV{RqNV_yS[ZnMdGY<eGvTzVwT[^`ee]]e_``_adcbcfflnturlpomrx||kbyZrXtVsTrUuUxU{U}V[^bfd[yQwPyR|TxQsNqNqNqMqNnKnLqOtRuRtRyU|W}XwS{V{U~X]`[\Z^[[[]bd``]]^^bccgehehgjonssr}ykqUcH|\@wX<mJ0hE*hC)lH0qO5sS8yY?cHwYdflmjdjggc]ZV{R|R]cZ][[]^]af\XU{RzSyQrOmIdB]<[=^>hFsPwRV[~RpH|N_`]|SzSS|QsPXXxU{W~WsRaB[=eFtOWxS`_`ccdejhc`begbcdfjjqqlcckpnsxtmkcxZvYtZv\zX~X|Y\^\acfi_VzRwQvPvQrNySySsOsPqMqOuSwSuStRvSxSyUxRySxS|U[Z~WYYZ\]a`_da\`__`bcidddfchmnnoopr~~rcsUmNmMjK`D_B_D~[A[B^GdMpTuXz]afghed]`dd\Z{SxQzRg\a^Z_aa`ee^YU|TzSuRoLc?Y8U5^<pJzRyQWYX|QQ^`\X{TUWjH^^vSvVxTrQ]?bBfFoKVzT^b_g`ikhhefgegcgefhlkibacilmepmjec]]\^YX|Z_^]c_cieaX{SwPtOrNnKsQ|VtQqOyVyV|X~ZxU{VY~X{TvQvRzR|UyT|UWZ]a^d^accba_bdbdgaeeeihhspmlokxl~^{WuRqPoNnMlLiLfJcInOuUzY}Z^aa`adc^^`cfa\~TuNYcdb}W`d`acg^^XUWtPrPb@W6U4Z;kHvOwR~WX]^X]\`\}VUYiF]`xWtTtRmN]@]?gHtO}U|VV`_kfglikjkeghgnjhhhi_dhkjigfijiefbaa^ZY_^Z\`]^dmi^~TzPxOxPtOxS{RuQxSX[}YzU|XwT\|W{UtRsPrPmLsQ|TWZYX_b_^`aaacbdfheeedgedklkggfg~qc}[yTvSrQsQrQqNmKuOqM}V]^aba`^]acefegagbZW~Wfef\]a^aci`_ZUX}UzTmHZ8V9\>fFqMtRzS[e^][Wa\[Z_mL`cxYtVrQiKbE`CdGyTzSYX]aihfjkpqkgoorupkgfhdikjfbbdee`cdegc^Y]c`^]^^^_fkbXyPwQyQ}Q{RvPWYZ{XzTzU}Z}Y~YXzStPuPvPvQ{SWXX~UUWZY[]^`ddcceabbeddeggedbb_dyoazYyVuTsRrRsSsRuRyT^eegfgd]\^a`bdikcghc^[bbf`[^bhdhf_YYZY}TqM_AY9\;c@mFqKzT[a_^WqJb^|Y[buVdi}`y]qSfLdIdHgKwS|U}VZY]igihptumlpwx{snhmlhkjjhmigga_effhgbabea`^a`bfhniZxOvPuOsNzS~UYYYX}V|U~Y[~XyRyRxRyR{SzP}U|T|S|S~UWS~SRY^]]`bb_cbbafefcdeaaaa__ymczZzVwTvTtSuSvS|Y[bggffhea]]bcdefnmmnjf`facd`gghffbc[ZYWzUrQgG^<[:^=hAsKuQ_]_axMrKc\XZ^]`l~`buWaH`FcGlLwQ}V}V]Ychhijwvtomotx|wmoqoqilsssqog`chf_fjhiifge`ab_aeik]YzN{PyPUUV|SyQ}U][_YxSzT|U|V~Y}V}V~WYW|SWU|Q~RYYYZ\`behccdacb`]ccc`_[``{si}_uVvSzUxUvT{TZ]`ccb_acfe`adhdhhltooqicdgbgfckifgcd\[ZX{TxVkKeD\>Z9fCoLtQ\X^^|R|R_\Z~W}T`djc`yYgMbEbDpNyQ~VYX`bikjqytupnptvy|tx{wvu{|yuqkgaaijmljhifhghdddccdjlq_U~R~S{QzSyQtMxR|V~XZ[|W}W{T[][[XUYzRWV}T}SVXYXWY\^`_^dfecccdc`c[[YZ\qpczXuSuRxS{TxR~W\`cb`]^_`dieccgklklnqoqqleihokkodgfaeZ[W~WX~XtSnLcB[:fCoMsP[_c`\Z^[\W|Sa\idaxYcGbFgItQuQ~VWV]eilkqpwsonsotxxvz¥~Ĥ~¢{ã}ťywunje_elpqlgiklnnlkhgbdhkqohb~TyQsPtOxQxPxPxP|U|UYY[c`aaa`^VUTUWZ\[YXYZ[Z[]abaeifecdda^\[[\}umczWyUvQrMzT}UZ^caa`__a_cjogdgkmlmpqpstojklnmotgkfghYVW{VZZ~ZzTiDZ:a?pLwS|Wdcdb_\^_`[c~Ynd~^tU]AfImLzUvP{R~U|S[cnqnplyzurqrttxxz£{ƨ£|ħĥĢ|vozumhjopggmqkgnpkoikkilllje\QwOvNxQqKvPuOrMtMySzTdc]`afhd[WV^ZXYXYZ\WUTTV\_bceccdkhgba^\]^tmc|[{VzWyVY]`a_ba^`bb`agkrolnopmkrjqwuolnrrquiiocf\{S}UzS~Y{V~[\lI`@b@qM|V`b^ggc`c_d[fsOoc`qP_CdDsOzV|VzS~U|TYcippnlz}zyuvtvuĤ|ĥ~ƩťħŨæz z{z|yvqsrlohjmqsstrpoqqppohfZSzOxOtKpIlFiDkGnJwR~W~X}V|SX`b_XYYX]YW}SVWWUWVUTWZ`dbbbbhgfcfa^^\\~uqib|\~[_^``^`[Z^aa[_`^aksxrspqsqqmpvvxqtrtrtkgmba]}SzQuN~W{W~[doOa@gEqM}Wda^gkid`_e_f}X``\pPcGgG~Z|WZ~U}V}VW]dimss}~}}vxxyzŦ~ƦŨæ~Ũæħä|¡zzz}~è~¤{usnjfilnliomqqnoqrnpidXzPwPnIlGjGiDjFpLvQ|U{S{S}UVZYXVXZ\[YVYWUSR~R~TTSUY]]`a`__ccgc`]\\_|uogfa^_cadc`]\`cfcbbcfgotxttqprsqqtvuywxztuioofa[UzPqL|V`a[vNb?fDtO}Wf^_chkgZ]ccgdxYe[nPkMiG[ZZW{S|S{QZciosz{~{vuyzæ~¨{ŨǧŦŧĥ}âyw£{ĥ~ä~å zvsmihhlrqrmmnoomsstrspi_YzOvNkFjGkHnHrMuQwQtNwP}TVXXVY\]\]^\TTP|OzP|QS{QyQUVVVX]\`__`a^][_abyriffedc`ebbcfdhhlkifeefhuwnrppsyxutwyxxyvtplf`ZUzOrL{T^d]wQhFeDsN~Yc_bcheiW~W`cddsSb\nNnOuQY\Z]zRX}V]ckqx}y|wvzã}Ĥ~¥~ç~ŬĨƧŦ~ŧ~ã{Ť|Ť~ĦêwƦ¢yvrrrlghlrrojlmmsvwvuvskf_ZT{OvOqKqKnIrLuO{TZYWZY\\\`\^\[~S{Q}N~QUWTzPtLzP{Q|S~T}RWZ[]^_^]`^]_`~unjgeefdd`_abefiknnmmmljinsrpoomuyzyyz|yvxwked^VWyNwO{S\hbyVkJ_AoO~Ybbdbf_`W|U}YijdrR[}VmNhJyVda]Z~Y}Z[_hmqw|y£~xwá}Ťƥä~æ~ǪǨȦƩŨƥ~ƧǪȯ¨~§|zyxvqlehjgnkikkkoqpprpvxpf^Z|QvOpItMsLsMxRzQ~SW~TVW[__aa\V[\WSPSTS}QzPyO}P{OzN|P~R~RU}STXY]`]]Z``xmljeebcca_`bcghhlqumqwuqmmppnpuutz¡|{{~{vnzzyqgc^X~TwPyRxR]la\lIb@pMzU^cb`_yS^{TxSzSfoi{\vRvRgIoP\eh`\Z]^chlrx£Ĥ{sz£}ŧƥǧƨǦƧǧǩȫȫʪç}Ǯ¤|Ħ}ħ}ç|¤{uoosqbd^beegihiihjlnkff^[V~SxPsMqJrNtOzR}S}S{PW[_`adca^\\YTP~O|P|PT}Q~RzOuKxNzO{P|S}R}R|R~SVX\]]__`|lfefa`^]^`achciloptrpvwyvtqstv{wwzv{zywunszzwojd_\{UzT}V]ajcbuPc@lIvS^dc`|ZrQ[uNsPyTcon~_tStThKwT`ejd_Z]bemsv{}~vv{ťŤƦǨȩǥƦǦǧȪƨǦȩǪȨĤ{Ǧ~ť~ázsrtvurlhgadddga_bdgjnmg`XT{N|RxOuMtMwPxQvP|T}SX\d``bbbYYYZWS}O|PRT}RwOwNwPzTySyRxSzRxQ|R}R}SVZ[^baa~sdba\ZVW__^ehfknnspltury{xvtpp}~}{{{yvpfknv©wsid`Y{RzT[^elf`qM`?gGsO{Ygf_tTjJYmJnMzUdnf`wToMsR|XhdfgcZ\cjosux¡}áxxyuğ}ƤǤȧƣȥƣǦǨȨŤǧǧǥǥǥƥǨz¢zowsjgadfghhed`^`efhhigf]UQ~P~QyNzQxPxOzP|R}SZ\]^_`acag\\XR}MyM{OT~R|RTW|QxP{SwPxRyQXT~SV[^``^]|sd\}ZzW|UWWYZ[^cgimmmpomkotwwtsqquz|ĥyrqrkjjpxtojd^UxQyU_agjf]nLcCbCmLzWaf_lNhI{TpMoNvRhhcauQsP|[]ejged~VZ]dipsvzyzztvÞ{ŢǤƣǡƣƤƥǦǧƦģ}Ǩɬǩä|Ũ~Ŭūèzz{xpnmpnibdda]c^cdhjkcZUVVS}R{RyQxOyPyOzPVXYY[^^^cba]WR{MuKwL|PTYYT}QzOzOxQwPzR|QzQ~SWY^___b~ymbwUrQpOtPzT|V|W}UWY_bhlmlknljprrtsqqoqsy£~Ħ{uppqoilutoid]XxS{W\hhjd]oNeFdGiKsS{[f`kLkMrNqMmMwSkfg}^hKhI^ddfiiaTZY\ejnsy{~|vyĥ~ƥţƦƦƥĤ~ȦŦä~ƧƦȪŨtqrlpxwzxwpmrsjglkebZ[`acfhjcYSWXVT}S{Q{R}T|RyO}SWVYZ]_fgd`[XS~PyN|PSX~SU|R}S|PxNuOxPwPwOyQ}SW~W_d^`czvtxnbxTnNmKqMyQxRyS|SW}W^flpnqpmpqqlmoprssstusw|ë¢~souzxmqlhd]WyR~]ajega_sRnMhIjKrSwWb^mNoPqNmKpNsSidh{ZiJdF_jbkdgc_\X^`fnt{~~ywĠ|Ťġ~ƤǩťŢ~yŤĦƨŨƧ¤|smciijtxz{yxusmjg]`_\UW[`dbfhf^\[ZW~R~S|Q{PzP|RUWYZ[\_`ch`]^[YUS}SWT|Q|RV|R{SxP{RwNrMxO{P}R~TV^aace~}vumkkjicxYmPkMnMvPySyS{TY`aciqqzwsrnrqsrrrswututsuê{u||qmhc_^VzV^`ejd_~\yWsQjJgHmPuW}Z}YtVrTnOlLsSqQo~ZgsRpNfGuSmelfhe_[{V~X]ckwçz{ãâšŧâƟÝ~ġäåƩĦŨwmenjijluusstppkce`YzS{RW[XTU]ahka\WXV~T}R|PzPwMzQSWZZ\__Z^a]]_\WUS|RU|P|Q{QU|RzPV{PyOxN{O{O~RTX[\_ddzvy{}|s~i}g}efgfc{]sVoPlNiKsOxSyT}VW`hlhqrxxxtmoprquuuzxzx|zstvuyypgb^Z~VyV~Z_ccba~\zYpQhKgGnPpTzW^wYxWnMuQyTnLh[`yVwRiIiJkkigfd^~W{SV}Sciu{v~¨¥ĤĥŦŤà¡Ʃƪ©¤}vttšyġysttupogmjnie~VuI~T}QV[]WXZ]`dg_]V}RV}S|Q~R{Q{QzP~RWY[]\`dbb^[WWVSW~TzPyO{P}S|R~TT~SzQzQwNvOyQ{RV[]^cfi{wsqpprwuwz~uwk}g|f~hi~ez^sVpSoPmMlLqNwR{V[[bffhmlqtrqqonopsuz¢¤}vqtxvja\YzUyUZ_a`a\ZwUnOiJeFjMnNzWa^zXpN{SwQrRcb]xWxTjJtT~^iedeb]}U{RSY_gs~|£¥ê¬DZŭ§ŧĩæ¨yĤ|ĥ}ƩɬȪǫƬ§|xsoldoplc]XTVY\[Y\\]acgdb_[YU}Q|Q~RT|R|PTY\\[[\\[VUVZVXU{QTVXZ\ZX~UxO{QuOyR|T}U|SYYaegissnonjnklsz|e{diki~eez`vZrViNgJlOiLnNvQ{W~Y]a_~[]__gpmmlknomouvv}sjltpe_X|TwQZ]_b`\{WtToQiKfHiLnQ^]azZrQ{V~XjL`d{\uUuSsQxWzV`cdc_]~WzTzU[aks|çêªūī«ŬƬŨȬħƬȮʮɬɫƪzyz{xupvtthe`\[Y^\ZZ]\agjgd^ZWT~S~QVTVPRW[ZYZZ\WWUSTWY^VWWWUVU~SwMwN~SzRzP{R|R|R~TYacca~{m~jlpr|e{c~ejoppor\iSlWr[zapy^{[z^x\qShMeJhLjNvT{U|Y~Z|V~W|VySxP|QU]eggjkklikqqz}~z}|pimk\}W}V}U|WZab_}[{YsSnRgKiLkMlQc_~^vUsSwT{VqQ^b|\qQ{WzWsS{X^ceda\zW{W~W_akw~ìï¬ŬƬëëŨŨŪɪʪʪǨť|vy¥~|trtvywsgdcb^a`X\^cehgb_[XWVTTUTSUWWYVXYVTUTVWWYZXVUUVXXTZ~T{Q{QzP{Q~S{Q~SX^\a_~}~l~fw_mvfs[s[qXx`~gixciV|\LaS_SbTbQhTgPoUy_w\rVjPfJdIjLwTY\[zUxStPrMnInKxSvLWadfjhhikoy}~~}|uidZvTwSzS{U[_^}Z~Z{VuSrQmNnPmPzYe_}\yVzWuQ}W|Z|Za~[qR}Y~[{ZzW~[aaa_ZxVzUxTzW_hz~ê¨ì¦ĦƦƥ|âyƦ}åy¡us¤wç}}}xtpqkjihce\VUX\_^bhbXTUWZXWYZYVVUXVVT}R~T~UV|R}UYWVWWWVWU~T}S{R{U{S|S}UzR{S~U[WYY\`rjxds\w`oYnXs]lWeQnW{av_mXybt]eQ_N}ZF}ZEvU<}YFq[y]y]sXmRhLeHlOtR{WzWtPqMjHfEhGdBcE^@cDlKuQ\]bdfgkpstx|zzwvx|z{{}|{yke{VtUtQuRyV{Y^^zXyWxVwWoQlNnOrSah_\{W~[rO|W~]{\^}^vY{[~_}^xYzX]^_~\zZuWrSqQtS_ju}~}ĥvrqrqsruvuqsqolgcgkjkgaYZZUW_e^]XTRTXWW[]XTUVVZ`]W[YY^^[XXYXXXWUWYYYYY~UUVWXXWX^aoi|eiTpYqZkVr]mXbPhUqYlWoY{ckTdPiR|_D}aGz[C{^euXvXuVnQhLaFiLqQsRqSiLdE]?Z<^@aCbEaC^@aChHsPVZ]c`gifijoorkejqnlprkptph{[oVoQtTuVwYyYzYuWqUsUtUoRpSpRxYiga_|Y`sR{Z^_vU_yZ}]}_z\wXuT{YyY}[}\z[uVnMmNvV^jvzu}slklttojijihfe`adggmmjieadcf_XZXU[^eg`V~Q|PRWZ\WUVUSV[\_YYUXZY|TXWVZYXXWX`_]ZWUXZ[ZYWWY_e~xn}g{coZl[iUjTx]L~cRgSfToZoZq\lWjVbQtVB~_Jz[F~_IcMrUx]x]{\wWnQgLaGcGhLlPgKeH_A]@\>`>cCeHdFdC_>`@lHtMxPyR|W~UX~X}TyTvV~_bfdd~d}cc{^wYx[w_}bktl{^lVmSpStVtWsTsSmQlMsRuUuWtUpR_ea}[\`b{WzWa`{Z|\{[}\{]z[sVpRsUvV{ZzXyWqSnPoQyYfrrzyyvtj}e|``ZZ[`aba__YT~O|N}OT[ekppqljeaa]XVWZ`fhgd_VxNxL{MTUU}Q|Q}Q~SUY]_]ZYYY_[VVWYYY]W[Z]Z\YZ\\ZYZWVZ]`rxdyfkt\_Mwbv^kUgTy_K~dQmWu]u^q[kUhT|cN}dMx[FxYFcOnUlWiTx\xZuYnRhLbH_CbGdHfIcE`@aC_A_@fGhKcGcEbB`@`>eAeDjIhHhInOlNhJiKeLlQoRoUoVrVu[uZoTwdIv\FnS:jT:s]EgWsb|eu]kUhOnQpToUiOiObIgJkMnPpPrRvS}Y_{YzW~[a_}\|Yfd~a{Z|^z\xZwWtTrUjLrSsVrVrTlRlSsYw_hrtt{{y}vj}bo\mh{\rW}][|VuRrMnHvRzUvOsKvLtKmFlHiGlKiHuNV`glpnih_\ZVVY_eloki`YSPPQTUQ~R{OzOzP~SU}SU]Z\\ZXZWWYYZ]\[_`]\[[^\\\YY[\_b{oz`mrsZqZpXjTiVfThVeU}bRy^LnV@ybLv\I|`O{cMv^FtUAx^FkPdNlTxZy[tXpTnQiLaG`B`CaFeJbEcEaFfJjLiLdI`D`DaBcC^;a?\<a?`?dDhJfIfIkNgMdJ~aGuY?oO8x\Dz^Jw]KgR;]I.T;%>$=+?4C5#YF2jY@sbM{cPhQiPiRiRbN_J^H^HdLfKiMmPrTzW}YxVyWbc`\`fmd~`_}`sWsVnSqWoSlRlRkRkQoToTsY}ciic~en]nXuaokcj}egVjPhPjWv]ce|YuQnKoMlLmNnLoIpGtKnHpKrMkJeIjK|V`fggbce^[YV\_dmieb^Z}T|O~O}O~QQQR}QR|OxK~OTZ`_]\XWUXWYYZY\]`_`a^_\b^VSSX[_`}pybs]n[nXoZhXhXiWbT^Sy\MpSArSCy\Kz`LgM6}`M{`Nx_IvZDuWAy`H~fMtWtVrUsUpQlOgKcG^B^B_DdIdIfJaFkNqRnOhKlLgGeGfFeBgDjHjImImJlJmLoOvRqRiRfOgF-f>)pQ7kQ8bH0Q6 J0?)(/!, '"+#A/U?)kR<s\D~`L}aL~^J~_I{[D|ZD}^F}_FcKiOpSwU{WyW~[`f][gjlg}]a~_rWoTnTnVlUiRgPfOgPmStXtVwZy_qXi`:XF0G-9)5&F8&XH7aT:h[@sbPxfTpdIkR=z^KzdjhbzTzQ{SxRvOuMuMyP|P~PQzNyLqImKqKXbddaZXVWX\]^eilk`\TtNuL{NyLyOzN}P~Q~QQSRQUZ^]^[VYY]]a\Z[]_ab`\^ch^WUUUX\\\su_gUo_vakq^fVfR}aO|^My\Jv\Hz`Ns^{eoXkT~dQ|eMydJoS=y\GrTmRpWuWtWpTnRhLcG]B]B_EbGgLiMjOoRuVtUmOlPkNhKnNnOqQvUvSxSwSvQyU|T[ZnUfSnP;U1jM3hK2`E*X>!G0<)( '$!#"/"A/G.\>%lM7w[Fz]J{_Ix\DvX@vY@v[@z`EeKjNtSwTxW}Ybh\\ilfi]yZ|^uWnSlRiRhRcMdMcNgQhRhR}cIt]FiR?^F0F25 .++2"8G/O;Q;cO5l^@kS<n[lsrke`\XYX}R}T|RzOoIpLxQwPsNuN{P}SZa^\|UUT~UTZacjkgb\ZU~SzM}PzM}P{O{PR}P|QUWZ^a[Y[YZ\ZYUXZ_^`b`Ydfdd_Z[WWWZ[_wmu[t^hoit`gS{_J|^MgTy_Mx\M~fTkn\m^fUx`Nu[GpT@eQ{aqZgOvZuYnTnRjMfK`D`FaHbIfLgLlQqTy\|^uWlNiMiMpPvTxTyV|V~V~X\Z]gl}]pTvdKS3eJ-hM5cH.\B&Q9B,1'$   # (9+D.N0Z;!nP;zZKx\Iz]HvVBuWAwZ@y^DcKhNnPvUvU}Zeh]{XhljhdvXvYuYlSlShPiRnVlTgTfV~cNqT?hK6bI0\D-S:#D./+) *!- 9'A)R;\D$fJ5nQ9hM6rhv{|sofhdaaa`zV}RvOrOxSvQsQsNrMuQ{VY[W~V{R|Q~PTY`hjnkg^Y}T{QyO{P~SS~R|QzO~RTYZ]^`ad`_\^^__]Y]```cdeddb`b]Z[Z\[_opXs]{fj{ek[dQnN=~]R~aUcTfY|aS{`Rm]udn]|dSqWCoU?~eew^uX~\wVpSpSmOjLeIaH`GbIcJbGjNrV{[|[uWmPkLhKiKkKqNxV|WZ\`dmoqiz]lYaC0cE-nS<jN8cE0U<$E39%+" !" '/<*F0J/P0^>%sTC{^Ly\HwZDwXDy[Ez^F~bKcKiLsRtT}[ddc]jkk~_e{\uXqUmSkPmSpWrZr\kY~bQjM4]C$]E$]H&ZF'Q>E52 *)+!. 3"I1[@ eI0pQ?tVApSD|ox~xsjif``[|VXX~T|RxPwQzVoMgFlJrOtQxSySyR~TTTX[^gjlie_[}V{R~STWVV{P~TXY\[`^_c_`^\^`a___\[\[_a`bd][WXZ\[\\dujs^lYoZr[q_qTFo]p^g[fYi[hZeWiYwbpY}cQv_IpX@rYBkW~f}bazXzUtSrSkNjMfJbHaHeKcJeKfKlQqUrTmQkOjNkNgKcEgGnNwTZb`dpu|}qyheV];&sV@qVAjR9YB(M6A).%&*!.%6&B/H0K1K/N2eF1xaL{eN{bM|`Lx]E{^GcM}`GjJtRxV^efddkkg|]]xZqTsUoTjQpUu\xazan[iW=J0R8YE ]J'YE%R?J:=+1 -!."1 <$R7cG0qT?uYCpQ<_Omt{vrmlida][VX|SyQ{RzS|V}TvOnJnKoImGkIuQ|SWXZbadjjda^[YUVX[[X~S~SW^`[[\]_c`^^][ZY[YYYYXY]`aaa\_XWX[\^`vdwckX}bSx^LsUAeVl\rno^iXq[r_o[iVfUy`Pz_Ou[IqWDsXEy\Qo^zaoY`JhOhOgNgNdK`GaIbIdKfNiPkOnQjNkNoRoRkNkNgJdEeEjKrQ|X}Y_eqy{q|ciJ2nI5v]CpZ>gN2YA N5?-2'/#0!8(@.I2M3M3L/M/U: ybM~iQjSfSdNbM|]GcJjMrRxW`fhcglhic{[vZuVoRmQoTy]dhbnYbJ5G%P1YA!`L/_I-[F*R?"K:=+2!1$8%J.\@(hS7uaDv\CpQ<qXlxyssogd_]}VxQvR|UYYX}UZY}UvQhGjGlGqMtPX\af`bdhhga^[}U~U}RXZYZX}RZ\^_\^\_]^[[][\Y\ZYTWZ]]cccd_\XXVV[\_}zvbt_mYx]JqQ@dOrapb~j{hrTGhZwen_lZ|aR{`S{_QwXJrWGoWBqWDy]PjXv\nWnUlQkOjPfKfKfKdKbIcJeLiPjNkNjNjNiMmQjNfJdIfHgHmMrPzU}Wdiptw{ftViH2yZBv_Co\BeO1[D#Q=G4A,A+D.I1N4M2L/L.K/R:x^GqZr\mXkUfP}aIbLgLqRuWagiglhmda~]wZuWqSnRqVamojm[\C.K*L)Y?!cO2jV@fR8cK0[D&O;B1C.M3Y>"gH3tZ?~gJsP=}^I{]p|zxqd^`_[[}SuPrPuOzT~X^^\]XyTuRuQuOxTyTVVWZ__ehfeb__ZUTT~VWXVVX]\[[Z\^^ZYZ\\YZ^[YZ\_beeaa^`b\\YY]^]vj|cs\yau\hs|uggM>zaRoSDoQCdH8}cTqbiZzaRpVDcG/eI2oTBs]FtZuVsTrRqPoPkMhKgKcIaHbKfOhOiNhNbHiMiOgNjPgMgMhMhJiKjJrP{Xeirvw|reiTrQ;oSpVxbIpS;hN/\B"Q5S;Q9T;!U;#V=#N3H+I)Q0{^Hr]s]r\t\mTeL}aIcInPqS`ghhghlbhbvXvXrTrU|]hrqil\]<*V5M.U5hM4s^Es^FqY?kQ6bG+\B$U:^D)hM6rXA|dKgLaLq`oxyqnkeb~Z|VtMqMoI~UW^d\\]^^]XW}U|T~UZY\ZY_fjfbb_]^ZWT~T}S}S~T~VXV\YYZ\YZ]Z_[\`__^]]]^aacffddb_b_\_X[^kiSkXzblyjnueyXNl_s[HjH9kH;gVyew_N|dSxdPpXEmS<pV@tYHuWGhPkSoUqUoSoRpRjNfMbJ`H`HaIeNeKeKdJfKgLgJgJiLhLgKbDcHdGeIlLuS^gmttyn{blPZB}``jS~^GpT8kL2hI1cJ-`F,`E+[A%Q6P1S2W0!|\Ju_zdway]qVeL~cHeHnLuR_fgjeglhe~`x[uXtWsWjmqvjkQ]C.Y9!X<S6bG)s_C}nOpKlJyaHqZ=mT8oU<qYAiRgOjN{bq{yqmeed\wRqMnIoLrN~S[]YV\_`^[Z|VXX~VXVVZ\]bhlkd_]ZV~T}R~S|Q{Q~RSTVXZ[\]^[ZX]__\Z\X[[]bbfd]]]`acb]]\]_bioZfYhXq]eVtf{f|kwZLgG7jK>h\{u^LoSBcI3nWA{jWr`Mr^HqRBz\K}dNfRjSiOlPkPnPoRlOiLdJeIiOjPnRgJeIeJfKfIgJeHbGeHdGaCaBfFiFlHuS~Z_ejktvhfsYcJ}\~ZsTiM}`Gv`DuaAw^DnR=eL5]A'[? aC)iK<o[}ege|^tVgJdIfJmNwV~]efmjghhf{^z^zZzY|`nruwpoWjQ<W4#Z:"Y;V9gL5iOwVyVyXkPbFfLfPjQnNbvywsma\Z^WzSxPwQxSzQV~X\V{S}W~WZZZ~V}XX[`]^_YZ]`edb`][\ZWUSR}Q{O~S~S~TY\\]\[^b\\b]^ZWX]\baea^^Z___bd^]YW]avu[pZnYq^iUiXdTmXmZsUGhZygzxgvXKx]LeH4bB0mQ>pVCoSBlQ@mRqTtUsUqRmOmOpQnQjMfKcIeLjPnTlQfLbIaHaHdKaH~[B{Y?|[?~^BaE_B_AdClIkJtQ{T\fliwsjdv]nXpUlSgMnToVmToVmYq^t`iSfQgUyagkj|bqXiOdKiMkNwY}[^cilhgdb_y[zY{Z{^lszuryic\{\ViH=iG7vWGnQ?gJ5`LbjzcoXlVrW{^`m|wrka~Z{V|U~UV[_]ZW}X|UzSwTuUxR}S}U[`\[YZ\[Z~TVZ\_^]]YYYZ[UUS|P{PxMzP}SY\^^\_^_]ZV[]^WZZ\]`]^_ecbed`Y[XW\^_rza}d{cfjSeQ~eQ{aNmM@gY|ixvjn`xfk\fI6dE1gI7x]LqYFhO<o[BkSpUrTpSpRmNkMiMjMcI]E]EcKfOjOjOfKbIgKlOkPfKbFy[=z]?}_B}\?~\@]?gGhKiKvP~W^dknv|xnd}]yYrTuYu\x]xcyh|kmpm|ex^}ejlnfvZiOgMjNmPxW|Yccggffecb`|Z{X{\koruprrq~izd|jqhvbt^t_v^tZz`eo{qtsied`[}V|T}U{R\\Z_]Z|VtQuPtQuRwQwR~XY[^^[\XVTSV^gd``\\[ZW~T{P|Q{PUTTVVXX\a^_`_`YY]dfbca`a``cbijee`^\WY[abyx~fo]{[KdQ{YHxXIx[HdTsbrahZl_yfvbudbH5u[HhVpXrbIr]CgP7u\EfPnUoTlQlPlOlOiMgLdIeIeJfKhKjLkMfIjMnPmPoShM|`C{]@{^@|^@~^A`AeEjLdFlJtR{Y[amtyzohbegifipruvqzcy_flki{[kNjMoPqQyW~ZcabddicccdazX|]hqosprzx{vrmplnghnvxnfa\YZzVwS{T}T|TxTuO}T}V]]]Y}UuOvQuPvSvR~W~W[]\]TT~QRR[^a^WZY\^`^YUP}P}PTWTT~S~RTZ`bdcd`\_ec`b``__^_a`ggea^ZX]\\`zxdqkYjXjVwSCjVpYeRjX|^Nn[~hjmscmZn[jXjWr\ElT:eK.tZ@gMqRoQmQjKiKdHgKeJbG^C}]A}^B}aDbFdIjNlPnQpUmRhO~aI}_C{\@{[?~^A~^AaCeHeGeHjMpNvPXbnuuz||wzpolqsrqmy`u_mTvZehbyZoRrTpRrS{Y^daabdfdbcbb{W~]ipopoq||wyyuyy~{xtkhb`]]{WzUyTvSwTzT{U}W}UXZYXXZX|VyT~UzTxSySWZ]XXXTSVUTX\[\``_`]`]VPzM{OU~R|P{P|QQTUW`^dgdbcb`][][^debegfdbb^]``__]~~k}iiVnWoYmXqZhQjTcQwZH{cwajZn[q\|hk[aUwYHiO9_?)Y: kS7eLkNkOnQlOiLeIcI`G_F[B|Y?zW<zY=z[?_FgLhMjOjOgMdJgMcJ|]Cz\@{]A{Z>|[=~aBgIjMjMoMqOtS|W^mjkqsy~{xunhev\iPeOjQuWc{]vYuWuWvXxW~Zcda^abbeccbd_aglrgik|{||}zwlgc^\YV~W|X{UvSoLzT~Y]`bab^{W{ZwU|X|Z|X~XY|X~V~X|W|UTUUUYWWWZY_cb_c]ZV~R}PzN{P|P~R~Q{O|MOUWX[^cdghea\]^`defdddcd``_`a^]\]_}}e{`~cmXy\K{^IePr\hTnYdRgTxdjZuaoZtbhv`mXaG1_B+lQ9u_Fy`IaKiQkRlShMeJ`F_F_GaG]CyX=wW;vX;w[?~dHeKfJ}cG|bG{_DdKfMeJ{^Bz\@z]?{]?zZ=|\?eElKlMnMoLuRwS{W^`dilps|~yuog{_{`lR_FcHtS}_yZwXwWvVwVzZ^cfc^_bacbceedbhjkfvXgzxpt§xspie`YWX|TyPzRySySyS{UYXb`_cee`^\Y[~XxT|W|W~WXWXYXZ]_]_\[\ab`a_^YWTS|P~R|P}R{P{PyM{ORYY_^adefd^^\[]^_fffcb`^dbb^`aa^^ht\t[y`|`MrS?xYEhVo\lV|e|^JnZw`o]vbvdqa~h~edI7lN<pU<r\Bt`FzbKgPmQlQnSlPeJ`H]B^F`I~^EzZBuW;wY<vY<x]Aw^Bv^@v^?y^Cx\Bz_E~cJcM|aFy^Bz\@z]?}_B~^B^BdGiLoMnNpN{V|VzUX]bdksy|}zwoie~[zYhNaF[>jNvXwWyWvXwXyY{[_bhhbadbdcehdebbif~]oRz]llkkx|Ĥzsnkf`\}T{R|RyRxR{SVW~W[\Y}YZ_b`a`^]bb_[|VuQyT{V}W~V~UVX]bc`^\^^`^_a]_YVU|Q|P}Q~S}SzPyPxM~SW\Zd`e`^\`]_^[_[befdcba`d_ba`]\^olVoXjVeTlXlWmv`}bffPjUuZw`}irsjVx^M|aP~dQv\JrWBnR<v[EeMoQrSqSmPfKcH~_B~_D~_E~aG|^DxX>uV9sU8rS7sV9sX:tY;x]B{_F{`G{_G|_H}`GdI{^D}^C|\AbGcF_DeIoLoNpM{T|VZZ[]bdorrt|wmdXvQfHgKcI[BeIkPvXwXvVzZ{Z_beiieadeffhegda``YvTkOsT`cgivá~}zqoef`]VT~UYYW~X}W{V{V{VxU|X~XYzUZ[^d^~[}Y\~X~XzUxSyTwS{UXW|SVZ_aXXV[]``ba^^]WSTR~S~T{Q|QTUT[a^`\\__``]\\Z]^_`bdfcd^_`^]\\_hjhS|_On[{gnZycp]p]pZcPoZjTdTgqv_wZJy]LcQy^NtXIoT>kP9eH3w]EhNjQiQfLaI`F]B{Y@|]C}`Fz]CyY>sT7pQ4oP3lN0pR5qV9uY>vZ?uZ>v[@z^F}`HdLfMdJ}\A{\@fHaEaFiJoMoNtOYYZZ]`eikjnpro`|XpNkLkLiKbFdEjKpPvV}Y]~\behgkcbcdjgfe\bhc]~XrRiJsR{Z_`iv{īnkhfda^\```^[~XYY~WY]^\Z}X|U[^dfhda\~X}X|TV}V}TUY]~VZ\^Y\XZ]`caa\ZYX~R~RTWUTRSWUTT[a`^[\__]_Y[adeffdbbb`^\Z\\Z\k}cmgRyd|fs^jUfSs\d|`gOnWjK;pZ|dtZG|_MfQ{aMvYFbH1uaG{nPtdIu_EjNkPgNeLbJ{Y>wT8wS9uS:xX?yY@vV<sT8qR5nP3mO1kM1oT8rV:qS9rV;sW<uX>z^D|aHgOjOdI}]BcFfHbEgHkJoNoLwR|T~VXZ[_cedekjb~\sSuQtPqOmLgFmLlMoOtTzZ^bdelslhadedeb`[~Xdd\|WnMkLwS}V\^ex~~zqkfbabbbccca]XXXX[^]^\Y^dfghgdgh`~X|WxTxTzS~UZ]`_[\\XZ[_]``b`c^U|R{QzR}UTU~TXZ\\XY\^_`^]aaa^_eefddffea^ZZ\\VXZxzeyau^lZkYnZzclWbNrZrr[|bMiU}fQv^HpW?x]H~eRydNq]gVt^HjU;bF/gL5t\AgLkOhMdKbFz]?vY<vX<y\@y[BxY?tU9qR6nN1lO1mO3mO3pS8mO5mO3oQ6oR7qV:y^C|aHiOiMcF~aCdFbEiHkIjIlHkIpMvQxP~UYZ\^[_be\X|V{SySqOiGmMrToRoPsUxY}]a_dnrojgfjjb_a\~W^^]~WmJlMxT~U[clstuxsmh`^_bb_aa^^XzVYZ`d_^_`ceebfb^][|U{U}X[~YYX[]_`]^]\Z\]_]^`_[XT{O~U}UvPwPzR}WYY]a_`]`b_cabca^`affhfge``\]`[^b[Z{dp\v_}gyfjYkW`OwTDjUpYeSqZ~biSlS;hM2lQ<nP>z`N{cStZMqYEiU<_F-jT9r\?~aIgNgMaH`Dx[<rU6vY<x]@x]Bz\BwY>sU9oP3mN1lN1lN2mO5nP4kM2kO2kP3mP4oT7sX=|bIjPfIz^@_CdGgFjIdFlHhDlIrNyQ{R}TU[XVZXWV~W~XzTySkJjKtSzXrSqSrRzV~[_`cglmpljlkfc`^{WZ^}Y}WyTuS|TV^aehpjkpngbb_^^a][{W^{VuRsOxSvQyWzV}X[_`][Y}XwTyRySrOsNwQzSZZXWY^b^[Z^aa]ZZY\_b^Y~R~R~ST{RuPrM{TY^\]]\[]_ca`cbcfacbdgghhkicid`]YVVlsXCqW{c~hv`oZiXgSrZjUbQu[}`mUlS8lS:{cNqVBu[FsWEu[KweOobE\E,hQ7o[=}bHaIgNgNcI}_BwX<xY>x[A{_E{]C{]CvX>sV9pS5oQ4kN0lO2kM1jM/kN1lQ6nP7lQ4qT8qT9uZ?{aE|aD{_C`DbGeHdHgEoMoLtOzQ|S~U}SXY~RS}S|RVV|V|SvPfDhGqOuTqQtRwV\]_^bfmosonvmeeb_|WzV~Y{V}W{VvS|T|UZ_ccohhgec_b^^\\[Z]a\YYX|WzUzW}[~\yZxWyWxYsUnNhMiKoNnOlMrP{VyUvQxPVX^`b^\_]`a[VUY]^WW~STU~S|QzOzPyOX\_`a`\Z\_ecdab`dgegfhlmldged]^]YY}~}dwaJgPkS}dz_mUxa{bpX`PwVFhW|gTuaL{bLy`KzcPmR?t\EnT@hL:iP;hT9_H/dK4q]B}dLjQfL_I}\BuV9tU8sT7rS7uW<wY?x[AyZBwW>tV:qR6nP4lN2kM2kM0lP4kP5iO2kP3mT6oU9qV9qW8w]?y_A|^AaE`DaEcEiGqOvP|T}T}T{QX\WTUXVV}U|RtMiItPuPqPoPsPuTyX^`diktvujpurifdb[yTuQvSvSvRwTuR{SVcdddijcdd`^aa```]~ZYXY}XZ]|[{YvTrSkPoPsTqUoQnMlNoNrPwTzU}VySvSxR|R~VZ]_`a]\\^`\ZWY\ZZ}T~SUU~TTyPyP{QTX[bege\[Z^aacdgcdejprollifbabb^YXvlU|aNfSjYs`hXkZiYs^kYg[}`OhJ5hF2`A.~gSlS?jN9iO9hN8qZFt`KfP5\F'lX<p_FiQlQgN`J}]By\<qS4pS3oR3pS4pR6rS8wX>vW>tV<oQ6lN2lN1kM1kN1jM1jP5hN0gN/iO0lS5lT4lT2lR2tZ<y^@bEcE`BaBbBfFkJqNsPySzQVY~URTXVXX{SsNiItPxRpOkNpOrS~\[bkmqvwnnurligj[{VvRpQoOvRxSxT{TZedfbbdccc_^`lg^^\^}WzUxTwT{X~]}^~\yZuXqTpQoRkOmNmOmOtRzU}WXXySwRyR|U~XZ|V}SY[[YY\]\ZY\]^[XWZVzNwOxPwPzSWY^dgcafcag[aaecbcdjlpkkhgfccc``[XsidnZcSfUgTgV~]OhTxYHk[wZFtT?hG2pS>rYCfI3]>%fL1dH2nUCjV>^F'\C%S6gN6}cNpThQcM~]CwY;qT5kN-eI'iM*lO.oQ5qW:rU;pR6mO3lN1iL.jL/gI,gI,jL0hK.gL.hK.iN0jQ2jQ1lR2oV7sX;wZ=w]?z^@}^A`CbDdDgFnKuPzQzT~VUUX[TW|V}TvOnNwTxSmNfGgGrSY`hlvts}ynnnsrnmiayVrPjJlJ{T~V~UzSYbljhfa``_c_^\]`\Z{WvTvTvTtS|W_}^{\vZoWkPfLcKeLkPiMnSoRoPqRrQuRwQ{UW]^[ySxUxS|UW|TVX`bab``^]YVSQ~PxNuMwN{RW\afigbc_c[_`a^^`^aehcggecfegdc`[Yw{_}b}bfx`zcioVgSa@.}^Jw_z_K}bMmO9iI3dF/bB+iM7[=,gK;]A*V;Q2[?'kQ9u[E~eOfQcO~`HuX;pS4lO/jM,gI'gI'jN/nQ3pT6nR4mP2jN/hK+hK,iM/hM0hM0hJ.gJ-gJ,hJ.hM/gL.gM.jP1lR3oT6rV9tV9yZ=~^B`BdDfEkGpKvQ{RTU\]YW[Z~RtMnMzTySkLbAd@tP~Yelr{{w{yplquyqphb}XqPgDnIyRW~W~WW`flegbb__`feb\~W{VxRvRtRvSvSrRxYwYtXqVkReLdKeJdIfJkNnPpSlNmOhJqP{TX\Y^[ZvSuUtSxSyTyUWZ_`cee__\ZUS}R~R|PvLyN~QU[bcdfhdbac`X\][^]W^bb]aabedddb__U|qs`kY|eht`hYaSlYp^}\NmJ9yZJi[gWaQtTErUDiVy`Rm`xgr]IO1\?([A&bJ/qW@{aLeOcN~bJeK{^DvX=nQ3jL-fC%fC&jG-rS9sU;mR7jO1jO1gK,hK,hK-hK-gJ,gJ+gK.gK.iN1iN1hL.gM.iO0mR4oT6pU7tX:{\?aDfGiEsMwQzS{RUZ`_][_^VuPlNwTvRkL]@`?sN]int|xxuyqlov{uphcYrOiJlLxT^[yRV^gijlmghee``_]}VuOrNsPrPwSvTrRnQlQiOfK~_FbJaE~bE^C~_DdFfIlOmNoMsOyS[`c`^a[}V}X{TxR{UzT{SX]]_`b`][YVUR~Q|PxNsI{NUY^edifedb__b`]YYZ\YY^^\]][[\YZ[WXvfx`nVq]kYeSm\qbsb|kbVqQBgXj\~cU}bR|^Pw]Nk[FuYL{gWzhWo[FmW@hU9_G)fN0qY?dNdMz_DtX<tX:tX:sX:lS1iK+jK.kM/oT8pV;pV;kR5kP2iL.iK.fH*hJ,gH*dE'hJ-hJ.eJ-iL/fG*fK,hL/lP4mR5pU7tX:{\@cFiInLtS|SXUYaba_aa^WzQrQyU{XkLbD]@nO[fqtzwvvwvqprutrlk[tOjJrQyU\}XzR|SYchjnolfede`_\^|XwTuTsRrQoPiNdI{\GvY@tY<vW=sO8zX=}[A^F^EaEcHcJhMoPrRyW]aeeZzV{U|V~XYYXTVXZ_[^]]_]YUUSU~RzOwL{OTZV\a\^_b_a\_caa^^\][^]\XYXXVXZb`]omQdpUs^~gn~gj}ezdnu_~aMcSs[DqU>rR?v\ImXeP9fJ7cI9oYHiT>fN8bD/fL4oX?s[DwYFdPy`Gy^CwW<pP3oS4oU5qW9oW9lS4jP2lS5pV;oV:nT7kP2iN0gL-gK,dG(bD%cE'fI,fI-dF+hL/gI.hJ0jN3mQ7mR6wZ?~_FeJkNqRwU|UVX^accfgc^V~RyT}W|XmMfF`?sOW_nmswzsutunostwij^pMoL~WW[^}U~U[bhjqlqmihfb_ddgb_|YrPkL~`HxXAqS7qS8uW;xX@}ZB|]C`IcKmNlNqRnPrQuRyU^`dbcaZxSuRvS{UzT|T~UX[]^\]^`c`[ZUT~R}S~SvKxNRWWUU_Z[bagfb_b`aacd``_^\ZXZX^_`_[t\lXcSran\o\q`aSr_vesb|ggT{ZJjO6fI-eB.tVCr]FcM5[;'V8U=!O3K)_A1iN<iN;dG4vYGeSgS}`KyXBqP7mI/jE,iE+hG.jL2jO4oS;rV>rX>pU;oT8mR5iN1hM,eH'cE$aA"eF(eH*fI*hL0hL0gI-iM2kP5pS;uY@~bIfMiOoQzU~VZX\^a_dedc^V}VZ{WpNjJdDxQ_jkjnt{urtvsksv{tj\rOuQ~WZ]a[YZagpplnuoiggfcfhib{XsRmK{fF{]B{\CwY>{]D}^F_GcLeLiPgJoMpOuR|U^^]XZ[\yRZ|S}V{TyT|TVY\^b``]Z]^^__YVTT{PxM{MPRZ[XZcbdbgfc__^b_`cfgdcc``^b`b_^\vq[wawazhr`}jdVj\t`|jyior]dC/`A$dG+hH3jL7mV=fP4jO9dL/U?W> eL3kWAiWAkV?iO8qXC}aNhQgNdM}`FwZ>rV9oQ5mQ6nT9jQ6mR8lR9rX?sX@vZBqV=pU;nT8kN2fG+dE(eF(hI,eF)fJ-eI+fI+eJ+gM1nU9u\B}cKjNpSmOxWX[Z^`abeifea]\Y|YuRpPfJqSenpoquxsslsvnrsy|m^uQyU\_ab`^bbdhnkltqkihdjijlj|\pUhM|bF|aG}^G~_FbHgOjPkNgLjPpSvUyV}Y^bdf[sRpNvRhItS}V|UyST}TVY\^^_a`\]\]_]\WV~S|Q{PzMRY\]^^ZZ^d`chd__]a`fechggbcdba^`YXmhT{et^{huep^kWo\wcvp}h~cMmO:eI2x[Gy\IhL7kN7hN5q[CjT=aI._G+_D.jRAmVDfO:jP<pTB|`NhPhPfOiNeLw]BrX;nW8lW7jW7jU7pX<v`F|dL}dM}dMuZBz_GtYArW>mR8jM2fH-dE*fF+eE,iJ/fH.iM3oT;v[CdNiOqRrSsTyU\^]`acaefca_]~Y~ZyTxToPoPcppjoproolpvtpqown]zU|V[\`^ceiklkgihllhdilmoqopivWkP|_G}_H}^IcLeNhOjNjMjOlQrRvTuR`abfe|YvQnNoNpPqQvT}WwR{UWUW\]`cdba^^]\][[~U|RxN{QVXY^]b`_^^]X[^_]^^`bcehfhgggegdg`^W~rakXv`dWse|m|kygm`gYp|k|hl[eW{^Kz_HsXBiK5bA,gN5jR:bF1_E.gT;T6#eH:iSBjVEjQBhJ;rUCz]LaP`PbN^JzWArQ8kJ1jM1eN-gM.mQ6qZ@{cKgOjRlSjQiQ|dLx_FnT9sYAqW>mQ:kL5gI/eF+fH/oQ;x\FdOjRkOjLtTzUW[a_`b`^``]VZY[|VzTrPqQ_joiiosnskntsmnrmg^Y^\Z_]^eklponjhhihinsrsrpjbsXoQgNaKdLhOeOgNiMlPoUtT|W[ejlha^xTsNpNpNtQtQxR{U|UyS|RW[[^`ccbheaZ^_[T~S{QyOzNWac_aba]_\aZVV\ZZ\_dhgddedgbbbb_[Y[zo_j[}gh[wiqp}hovbotd{fjWl^}_MrXAlR:gJ3fH0vdHkS<qXCo[Fm\EgR<_C.dO7gV>fN9_D.eN6y]HhReObMaIxU=qN6jH.gG)aC#fF*jK2rZ@y`KeRcOdOdNdN}bL{bK~eN{aK{bK{aM{`Kv\Fz`Jv^HxbJ{dM~dNkSlRgMiMuTxW~Y]`eca[]YWYWZ~Y{YyWvXqVgmokoqwrvnloqoopjg_^fb]]V[agjmrqnkjlnpsuywnogvZkShOhOeMiNjNkQjNtRyW}^a]cgpog_{VqMiGoMrOnMrQxQ~U~VWXY[__^abfeghacc`_}TvNyPwM~TYadabaabbb_[YYTUY^cggihaa`]``_^^^\y}etcqq`n`x[N~_P~ijnsttaiVn[qRAjK6iM2gH/eE.u]FmT<jQ;pZFo\FdM6U8#Z>([@+cG1`G.aI.uXB}fLiOeMz]CwV>uU<mO3jO0hI+hI-kP4pW<x]Fx[F{_J}aL~bM~bM~bMz^Iz^Iw[Ex\Gw[FwZDtXBt[Ds`GveL}kSpWvX|\h|_uSoNpMZ]bba[~T{SzQyR|VY|XyVyVxYuZonppuvyw}zlplonpogecie^]\W[abhqrtmmnmsvzzvl}cnWgPcKfNiQmRnQqRsT|WWafkjg_~[~YwQgGqNpNuRuRvQzUtQsN}QUX\``efffbiikiiedd[|UwPvNW[Z]_aaad`cggb`[ZTV]`fdjjfa`][[XYYY[}gvbtdn`j\~\PhYqUB{^OjZs`xct`vZHv\HtYF~dNmT;kO9bC/uYEpVBaD1gJ9cL6bG6dF7lTCdJ;a@1Z9%R3^>({]JcPdO|`J|\FxY@nP2nP3jM0lQ3lQ3mR5oU8pV:y_Ey`FhNfN}fL|cK{cK{cKx_Gu[BtW@sU?oS<nR;qU>x[F|_OrZgqvwn`dha`b^Z|T~TzRvRySuRrQvS}[dlqrtx{|}qqlmqtridh`b`^\Y[Ziu{xmjmnlmnkngx\kUkSiQhOjUpUrVvZy\_bhijig`a~UrMlNlNlKxR{WzUyT{U}TUX^bcfikonnmpmnhfbbZ~RyNwNVZ^]^a`_bcbbad^ZYWZ_dffimmg]Z[WZ]\]Yqzes_p[lXt_ksu\}aL}^M]OcRvYFv]JzeOhSyaJoZ@r\Dw^JxeLq\DkR=hP9dO6lWCeK9cN;hWC_C/aG.`H*_K/n\BzeNmUlT|bKsU<pR3oP4pR7mO4nO4mQ4hM.jL.mN3tY=|bIeN|cK}cK}cL|bKx_Fu[@sV;rT:lN4oQ6rR8tU=fPw[fpz|xlie^`a^bZ|SzT|WxWmNiLadmqsw~ææ{srpprttqnfZab``^aalrvysifqq~\tR`ji`uSnQpRuWu\w]~bbglpvuke}Z~YpNoKtMqOsPuQ{U[_]_^]cgjjlmnmjpsrqrkc`[XTzOzOSW\^^_a][_a_aX`ZZX\_cbbikmlgf_]\[YVZovaqcrUFuWI`Srfwar[wWHZObPzco]}`PlTz`KxZJxbKs\DpU?iO7r\Dq\EgS9cO1dN7fS>gWAjYDfP<eT8aQ2aP2h]=ugLmUjT|bKz[FzXDwU?vT=vR>rQ;kL0eA'b>#kH0sT>{_L~aO~bPgTgSdP~cP|_Jz]HsW@xYBrV;pT;~bLqX~`fdolyzswwkdcgh_W{U~Y\~^\ElUz`jpsv|ĩ{vstqvu|vh{XvSeddeekoopfjyz}ĭ{vzynbsxtswvy~peXXVW^^abceijgglnmptpqmrqttqmgeaZ}SwPuOvO{SWV\`^d_a]`b`][][Y[`cjhejkkjb\[\ZYXZ}fp]yTH_Ro__TeW}_MeUbTlZs^cScTxbLtXFvUGyaNvaLkO;fJ4nVBiR>cG3]A*`C1kTFlXI\A3P0$Z?-_C/S2!Z:)fN7waKhTbPcQdQ~_J~_L|]IuWBnQ9hJ2gL4oS>w\H|aPkUrYq[rZqXoXlWjS~cN{`JkQpTwZacgf_\_isxmifc_X|VX]b{[uT?u^~hmqtr|~wrpstvvumzYhK`ifebhnsvoku~ĮŲʹȶűvjz[p\q[s[u^{_ekntvwsffa~XxSuV~Z__ecffejklnptssrsnqtsuspjica\WzQyQzR}SRWXX^`cZddaab`][YY^bgkjbnlng`]\][YX{gw\|gOv]{aklv\w]q\nZcPr`dRvmVAwWFv^IybNoQ@lR=q[FoVE_B3\A,S6%iNAeO?_E6_D7gSAcU<`M5]G.dQ7r`EkQoWqWuXqWlTlR{jMvgJudIxmPzpT}pTv\v[{_fb|`y^y[uXhPhPjPjP~bIpVbfkexYkMmMtUgoupnh`[ZxR|U`ggmNzW@sYyd{kinrxz|wnkmopmnlz]dI~ZgokjgmkqvzzˮɲǶƷı¯}rnihghhkllnkkf~^a`ccgfhhllnnpqyxyriklnnrsxxprphgd]T{OzP{RTSX\][^b_^cefc_\[Y[_aglijejmpacbb`^\yrY|aOnUD}f}hzidQcOzWDp[}cx`p\kYzlQz[HtZCyaIvXFybNjV~fWqm[EbK7mZHp`Ol[JdQ@fWCk_Ig[Cg_Aj]@t`HucKxcL~eNdOfRbN{`Jx^Hw_Hs[Dt[EpYAnU?lU=u_G|dPkXqZt\s\nVhR~dNfN~fN~cK}`F}aEiK{Xeiee~_tXnV_hllhe\\|W{W^gncyX<UBgVk\rbxg{ilqlnk~i|ik|hyhy`oT`K|alonjjgkoz~ɮǰïª~nfp{kkknnqkkqxphcccccdcdfkosÝursonnspjjjhgciqttsoimea]}RuMyQ|SUUZ_ad]``^^`be`X\Z]bdefkljjjlkic_\]]nYy\Lm^kZ{jux{hjXmY~`M]Psx{ffW{aPy_L{bQv]MwaP|gV|iXkTBeL<kUDcN<nXLmWKo\MkZHeR>aJ3`F.^@)lQ<tYGzZKzXH~\K~ZJ\MyXGuVElK9hD1gC/fB/dC/jK7pUBy_NjWlXlXgTeR~bP}]Iy\Fz_Hz]Ez[@}`EeMsX{^~bkryocacdfed`\}W~YbjqsZmF4wN;_MkZscvjxgycn]m^xgyhxhygtaiZdT`Oxbv{znkhjp~~|~zop~~xjhnmohgwXtVb`ccafkiklpswxvuvtmnppikkehimmoomeaa^`\{RsOxRXXXY^a^aZ`_]]ZZZXXZ]adiikiijkjomojc`_pXgSjVnwawe~jrrhVv`lXygu~n{jo`{]OuYK{`SoUEoWFgN<bE4^E1xcUcL;gQ?r]Pm[NjYHfU@aN9eS<gV=kZAtcM}hT~ePhR~ePePgP{bMx^Iu^GqZBkW>jW<mY?s_Fs`IyeO|gQlWr[qYmUhSq[t[t[rZoWsZw`hgw]vZgnopg`uR}\c^_[~W|V|YamhbIhF/h@1|RCj]nhqik^vOCf>5wTEn\p`i[^LyRA{VFmXqujkjv||{qhmy~°´ó|{xmd{\}aljdhjommstvy||~|yqsrrmkmljlhknmkd_^YW}TyRzQV\]_a^]_a`]_\Z~U}UYZ^^fiikkfinnkfkqmd^`clWz^Ov^{c~gtgpku`ijikZmXbNuWEy]Kz_Nu^Kw_NkQ?aG1jU@lUFfP>iWChRBgSChXFdT>ZH/]M2eV;i[Al[Cq`H{iQzgN{iO{kP{jOmR}jQ|iPygMugJq`Fs]Ew`J|fQzcQ|gSq\x_~dikrzxrwrpstn`wZ~]cltfazV[[zWvRzUwUzW_jk}\q]=jD0e7*sF8ZPhZfZkA4f;,oF8fSaO}TDqG.vP:jQ`x}tnnlgmnollqzyihonfeutzutsqcfpsvy¥{¤z¢zwy©Ī¥~{wtppooihkjnknmhc\XT{QzQSVYZ`dbac__`bbb_^\[`aaddfjkmnpqlimkiga{qWn[{bOhRp[kljsnspZyhotaeUcQpT@w\H|cPxaMtYGu^Lp[GrZJqXIjVAgQ;cI4eO:l\Hj\JhXCYI+\J-cT8dV9dU9iW=lW?s_Ft_Gv[Fx\Gz^K{]LwXFnP<jK6hI3hL6nQ=w^JzcPgUs]v^gkmliv\u[uYsWqVt[x_v[rSqWrT{Ygki`~\]~\wWwWqTyXbgeitT{\AkG0f<,j>.YJYMzRDvM=}TC\K{VCvL9YBoXlm~{rjihhpusu~|uussrlb|[jnje]jnry}ƯŭǬëƭŧ~ƪŨâzĥ{£xxħ¤~ê{ã|£wopoomilrspmiebZVwPxP|RWZ[]^dca`cea]UZ]]\`cegdabfinnpnlmmiffjq^kYiXcT|aOw\It`mlu}ip_|ibVcXaSz]OtWIuYIgH9y]OzaSybTv]Or]JkXAfM6\C+pcIoaKfU>ZH+P=R=T=S;\C%^I*eP5mS>pT?rU@xZGy[I{^KqUAtWBsXCtZDv^Hv^Jt^Iv`Ku\LwcQ{fTm]o^~gTxcNs]Kv^Jy]JvYFw[HvYDy\Hx\H|_IcJfNmSoStXvYvVrSyY~[^d^}YwWwW|Z`cjpZ|ZDnI6kA1wJ<yL>tH:pB7yN@~YGwQ=ZIpZjqwxywvsplottsz|wspf}cx\t]x_f{[zYentqttkmhknpurssyy¡{ĥȭ̱̰ʮǩŪçĤ~ä~¥~¤|yã|£zvqnlklltvllggd_WxRxQ{SWY]]][aiccdmldfd_[[]chmgkghjjhllonjfdtlWvbkUs]kYs]tT@cQmZvas^sipRDwWE|^MwZKx]Pp_hXsWIw[My^PsWHoVDlTAiP<fM6_M3ZF,P:K4L5P;VA [D$ZC$aK,_G*dK1jR9pY?v]Ez_I}dN|eMz`JwZEqV>kO7kN7eI0hK4iL8kT>mXAmV?gQ;oZCvbLtbLt`It[Ev]Gw_Hu[DtYAtYAv[AxYC|\GfNoTnToSmPhMmOrPsRz[`c`yXuUyWy[x[arW{YFpM:jA2pJ8kB1_0#k@0yS?_OlXkzwuooprvxzuvpmoplcchlemh}a|]sTyY|Y_dr|zqtwwÝywtsprttäwä|vuw{ħ§~~}¦{yyutlf_fkpmmmibb^Y|SyQ}SVUZ]]_befcdhghbgb`a`bdinnjjijkmmijhgezs`muZt\s[htpjjYkZ~`OpQBoO?uWEoY{`Px]LjTkVrVDsYDuZHy\NrWHt^IdJ5N/iT<]J/R:$?#I3T@!ZE'aM/cQ3hV9iU;nZ@r\Cs]Dw^Gy_Iy_Hy_HuWBqU>pT:gI/eF+eI+gL/jO6kT9gQ3dJ.hP3jW9o\@mX?oZ@q\Br^Cr^Cs[BrX?tZAtZ@v[Dy[F|^HgLoUx^tXrUkPgMgLhKpS{\^\vTvTwUxX{[`pW`MtS@mM8lF4f=-vO>_Mp]lsxxrsnnruvtoigjfhrxvturgc{\afebciow~~vrpturqnpnrqpmplmsrxwwsxxtkg`hmkikh`YU|QzP}RV}SX[\acfjfgfe_cfhaY]a^dioknlhffkighdcmsao[mR>pR@rUDt_q]u`jr]uabT|_QsSCnN>uXFoS@nP>mP<tVDuXFv]Gu[G|cQ}iUyiSu_Kt[IgW<WF'UD#R>WC$^N-cV7hX>lXAp[Ds]Hr[FsYDsZCnR<oR<pT<tX@qU@rU=oU:iN2dJ+dK*cH)eJ.cJ,bG*_A&_C'`C&eJ0hO7kU>lV>mW?oV@sZCrZBs[CsYBw^GzaJzaJ~dJfPjTlRmSmSqUqTpUjOiNoSqTqTrVrUvXsWqYu]kXbRz[HsQ>vRA^LoY|fkmljilrppp~bx^|acdgkjice{`w[pRrWyabivjloijmmkicu}xuuullmicfkjjihklpttqmkklkhgfjhgf_}UxOuLyO{R~SUVX\]dcfkhghdb\a^]`_^`bffijnkhefihhgkSiSfK7jVhTcTqZs^nw_wr_eVdShV~aOcA.fD0fG1tUB{[L}`RtWFwWFw[Jz`OiWk[oZtgM[J+XC$[I'`R2eU<hU=q^Ho\Fs^IzdPqXDnT=iM3hG.iH.kM2oS9rU>sW?nS:mQ7hL2fG,`?$`?&`@'^>$[=![>"\@$`G*dM1dP4fP4iQ7lS;mU>pXBpYAt]Ev]Gv\Fv\DvYAuWB~_J`KfOlToUpUnSlPkOmQoTmRlPlRlSmSlTlWmYjWcR}_M]MfUway`t\sYtZ{^cgikii~a~bcadfdc{ZlHt[At]Au[>{ZD[IdNlVnTtSbqysnvpopqoommonomiifhgfeadhlkqtoljliijggbea_TqKpIsKuOyPU[^_b__dfhgdcecb_a^``aeefdgkooicbhefxlQbH2|`JgOhQfRyaza|hkK5hH3vWE}_PdSm_ofRoS@mZv\HdSiXo\wZH{_OiXiZvZMs\K`M2S:!S4_I,bO5gP<q[FbOeSjT{bMtYClP5eG*c@&b?$gH+pR8uVAsWArW?mP8iK1dF)aD&`@%Z<X;[> Y> ]D#[C#\C$\C$aH'cL+hO4iQ8jS:jU<kT<lQ:lQ:jN7lQ8uYCtV@sV?tY@x]FcLfQiOlPkQmQoRmQjOjPiPjOjOjTfRhTdOz]IbPiUrYnWlUoSt[{^bx`w^dcbcb``}a{]x]nY{aHyeFgN1hL.qS8vX>yZA^IhPnTtX{[^agunyw¡{rjekprnmmllmilgehhihifjnprstmggildggdcW~RsLmGoHtMyOTZ_bcdglnlfbbbcegcb\aecgeeihikfcaedgzdgQfRlWfP~^OjWhTqR=]:&yZIzfzglwgteozeq[gToZiSz_NjXiVt]m[iM>nVAbK2P2aK.kXAt^KjRnWlWcMtY?qU<oO7kL1eE+cC'gI*oR6qU<sY?sZ?oU:gL.cH)^A#Z<Y;W8X9X:V7X9Z< Z>!^B&_C&aF)dI,dJ.fK0cH-gL0cI-hM4nT=qV?jM6kM5lL2lO7qU?wXC|_HdLeOjSmTjQfOhOiOfNfOfPePbN{^I}_KcPgQiSkTiRoTsWvZ{_|a{ax`|cybzbvas\rWiT~bN~eNzbKpS=jL4s\?lR7oP8qT8vZ=_GfLpLvR{XX]heinwstsrmpoojknjiklfegdnlmjiotvĦzwpomhfigff`WqKsKrGqIvM~QVX]^bejlppkjedc^_a_f_cecacbdfihb^]ab{fr[sallgZdRw\FyXGkTlQeOv]nvrwlq_}`P~_Qn]n^z[Nx]LsVGz_N|aR|cT{hVtiRdM4cK2u^IiWq[sZmVePbJ{]F{YDxXAnR8mS7mR5oS8sY>rY=lS7fI/fH,`B%[<[<!Z;V6U5N/V6W8X:W:W9Y:[=!^C%^A$`C&bI+cF*gK0iP5hM2fG.eF+gF*gF*hH,iK/lO2sU<v\B}bIePhRhQfPePdObNcOcOaM^K^I^JaLdOfPiQjRmToVrYw]zav`yax`s[rZjTePr\CiT8gI2jK6iP6gL1hJ/iJ3qZ>t^ApU9zZ?[CdKqNwSyRVZ_bfhckuvnqttupighhlokmjnqnkknprs£vrpmilmgigf^XxOsNuNuLzQ}SWY_``glospmhied_a`_\^afa]b`___a``_^\ynpwekp}aQgTaMgUlWyZE{WFo[xpvaeTdV{^L_OcUcRy^JqW?w^FxaNkVt_ucOs\Kv\IfRpWvYuXqWpVlRhNdP`L|]Hz[EuW@qR9mM3cD'iJ.jK1eF+`A$^A"W9W9T7R3T6T9V9Q2S6U8X;[> Z?!Z>!aE(`G(gN4gM2dI-_@&a@&bB&dD)dB'eE)cD&cE'iK/kO3sX=w[E{_I}cKdNcNcNcNcOdPcNcN{]FcLdPfOeNfQhPjShTlWmWu_o]oYp[{dQt^GhU<eK3dG.aC'`@%_;#b@'hI/dF*nO8kK2rW;~cI~_D_DbIoKzQTW^a_[bghkpsqmpqsržunjdmrrlillknlkmmspvrpjdjjgaV~RwNwPvQwPzS}U~TWX\\acghhjime`\\]YYY`ac_\^]]^^acdc`wcmitqzdq`lZeTeW`?/sP@k[vdrzkmt]hTcPt]~dmV~eQt]EhO5gK2gL5s[Ju_NlTGx_PmXu]x_wYvZwYzYvUoQoQmOmOkLcH{\AtV9pU7hP/cH*dE'^A!U9W8P1O0R5S8P2K+R5T6W9[> Z<Z?![A#bF(aG*dL.]@$_@$^?#^>"`@&dB*eD*fF*cD'bC&dF)gK.jN2lP5mQ7tX?y\G{^JcOePgShSgRgRhRhRiThRjQiSiSnUiSiTjTkTiT~cPydMnYBfN7dF/bC)a='_=&]:#_;$_;%c?(iF0nO8tW<tR<{U?dHmJlKrNTU[_^]_baejgbfpqoqqtt¡xâyrjkifikmklkjkqqxurjdggc`Y}SyRxPxQuO{RUT}RTW[^_`bc^ffhea\Z[\^_]_a`^a\[^_acecukXu_oZvcmWbR}_NhTbP`M|aNkZu`oqap^u\fMz\I`Pr^mZ{_NtSFiM8\<'W4!dD2jL=jM=cM7u[ImUx]|_bb`]Z|VzTvRuPoLkMbFy]>rU8kJ.dB&]>!Y;U5P.Q.O0P1Q0P.P0Q1V6Y; [="]?%[?"`B&]?$^@$\=!^?#]=#^?#^>"bA'cC(dD*fF,dE)eF*gJ-fJ,bC&dC'hI,kM1qP8tS>vYCbMhUiUkVlWnVoXpYnWjSmVpWoVhRtZDp\Dp`ErYDpUAiR9hO6cE-`=(_@'`B)^:$_8%b9'b=)iE1rM:sM:y[AeH_GjIoPuW{V}R[^Y]]`a]^deiggjpsrpmnlq¥yxvsplegijkqqrxwplbhle]}S{Q|T}TzR|RVXVTX]]]\]]\^bce_b_a^`^_]^a^a`_[Y]_ddf~g|aP}`Qq]p]bWpajzfjVgPjUhUt]u]ublWnWwZEtUAeTvceUsSDhI8dL5gTu_s[GwaKkT=xaKnUccddifba}[yWxUuRsQpNkMeG}_CuU<oO6fE)Z<W:Q0P/M-N.P0R2V6V6X: W9[="[="]?#\>!Y<[> \@"\>![> ^A$aB&`A&dF)eG*dD)eH*fH*eE)dE(`A"[8_< dD'fD)iI0lN5sXA~bLhQmVlUlTnWpXnXkVkVkSeOqVAiM7aD,eM3gK6`D,^B&_@&\<#[;"]=$bE(bF)b='f@+iJ0oQ7zT?XF`FkIjItLxTwXZ]^`dbeceghihgjjkjlpŸvplkpqrsy|spjhfhmrtuvxtlcdc[}P{N|QX[WY]YWXY[[YXXY\[Z\cbd`aac_ac`^bba]YY\ac^~ghU|`NlYeS|]OradT_RaQ}bN{]NoN>~`L{_Mz\NaRpT>rT>}\LhVhR|cOtWEzbNmX?rYD|gQ~iU}qXoWlWvZ~_cefhefcb]}XxUtQpMlLkLeG_DyV=uR7lN/bC#Z8Q/M.K,P1V8X< Y>"[@%[?#Z=!Z= Y<X;Y<Z@ Z>[? ]@"_B$aD'aC'cD(dE(dE(eG)gI+dE(cB%`>\;_>"dD'_?#`B$cF(gJ.nR:sZD}aMz^Ky`J~eOdOfSgQv^Iw[IpU=_E(\@$^@%[<!X<V8X6X7[8 ]=%]>&`B)gH.jK2nL6xV=_CdLfRiKrNsM~R[\Z_fkljkkmmnnkkjkklmiflpsrokidfktqmifafjnqwxrmcXPvMvMxM~TW[\_`_[Z\\][Z[]aa`abcd_`_bceddecc`\ZZ^b`xau_efzb~c{eq^o]oez_KoQ@oZs]~bT`Q}aMlXz]M}^NuXCiM4gVeYw^Nw`NtZIrVHzbTr^wdchhebhicc`\YxTyStOrOnMjKcI^G]AvQ7pL4hD+_@!T:O2T8W=[A$[@%Y?"Z?"V9Y;V9W:X<Z=X:[>!\= ]>!^@$cE(`B%dE(`B%dE(dE(cC&`A#_>_?!aB%`A#^> Z:`@#]=!_A$dF,kM5fH/gN2iN3kN6kO6nT8dM1`C(`C%X9U5S7X=V9W9V7Y:^;"b@'dE+lL4uV<x\=]EbLoPuS}YxQ{SY\fefgn™qsqhnpãuonroookkimmgfnqmlhgeeddloupb\ilsupnia|QqIuKzO}UWY\_^^_]\_]]]^_eaab_`ge_\]`_b`bca^[YY[_`utc{f}ePmZny}gz[H]LgQ}]Jk^xer\~_ObPePjWcRvWGY6"a?(hH6vWJqWDkS=nSB{dRub}gjcdfjlddc``\Z}U{S{RzQwRpMmKfJbH[CyU<pM7gH+\@ S8R4T7X<W:W;V:W;T6V9W:Z= W;Y<[?!Z=\@"]B%^C%_B$`A$cD'eG*cE'bA%aA$`A$bA%cA%`?#\:Z8Z8Y6Z9Z:\: ];!^>"]<#]:"_<#Y5X5U2W4T2Q.T2V8Y;Z:\<_="gC)oJ1xW=^EeJlJuPvRZ^`a]Y\`qvuwtqáwtnmpãuâvģwâtqnloqpnmlghgkidfdbaejpqojkktypfdaZ|Q}P}P~TX\[_^][[\[\]caac^]`]a\`a^Y]ZYYZ\^\ZVXX\[vo^iXjVo]gXkYwYHyXFwdpq^sbves_vYFz^KpZy^LcRdQeQmP<xXH}aPrzeTu_Nxdjjyd~bggjkegea`[[W[YVySuOyOnMfJfGbC~`CwY<pN4`D$U8K,P1Q3O2S5S6U7V9V9Q4U9V<W=Z>\A"[A!]A#^A"^@!`B$aC%aB%bC$`A$`A$`B$`?#_=Z9Y9W8W8Y;W8W5X6X6T1W2V2U1T0S/N*S1R/V4Y9\=a@#hE,nL0xX7}V;^GiJuO~WZ_bhkmkfefgrx§ƧƤʨǥ~ȬŦ}rãtsťxŦwĥv¤usqät sronjmhfgfgccdcgjlkmlouyys`YYZRS~RVYZ\\]Z[Z^_bba`_[_^`aacZ^ZXY\\^[zNSVTVUSTpZjWoZq\vlx`mUr\iqrm~eTuZHuZGwYHn_gXk[kN;vVEj[~nxcThK>gYvdzhwcfegmkljdeX~UW]^b[]XRuOrPnJpKjIfGZBnN.\? O.F#K,M3P5T7T6S8T9U8Q4S7W9Y=Z@!Z? [=Z;`C#bE'cE(bC&cD'_@!aB$bB$_@![;Z;X8X8Y<W8V4W5T2S1S1T2T1T1R1Q.T1S1X7Z9^=!fE)nN3rU<}\DeInOhPrU|TY]^bgpuvrllnqrĥx uģ{ȩǨ~ǧ~ƨ}ƪ|ŧyĤvãtäuƨyĤvävƥxǤzŤyĤw¡strqĠtrplnnigfcfomonswvvpcVQSSTXVXZ]^`_`acadcd`][\]__aa`[UXZ^^a`a^ZTR~RSSqpZr[uar`q^lZeQp\iXypmZbRt_bSsYEoT>}\LbZfZ}aRy[M{\OeZm_|iYqWFaD2{cSkYkUfghklmlib_^__fea\[XR}RyRwQpMpNcHvX:dF'\?J*D#K2N5Q6O4Q6R7Q6Q6R7T9Y> Y>X=Y:Y:_A#cF(cE(dG)aC$_?!cD&cD%_@!\;]=[:X6X6V4W5W4U3O*J%S0S0U0V3Y6[9!\>#bD)b?(hD.sJ6}TA_MjPuT}X^[V~RX^`fmqrt£ysnnqâuģwģxħyåv£uâto rqpžoàr¡tȧ{ȥ{Ȧ|ȨǦ}áwuĢyơzŢyȣ{ĠwŤwĢvpo¡tnlfj£urrrvvtqgXQ{M~OPUWZa\]_`adhgfedceb`\]\`]\\[Z\^aaa`\\U{PzPxM~PwahVmrsdRxd|}et`ihTm[yWJeUdSz_LtYCz[GdUu^u\z_OxXJ}\Or^n_yaSu]NhPAmRDnXdeilnpmgg`adcdca__\ZS|R|SvQpNnM`BnO4dF&R4C#C#L/P5P5P5Q6O4O4R8W?U;U;U;W:[=^A#aD&bE(aD%`B#cD%cD%bC$aB#bA"\;[;Z9V5U4W6V6R0O*T4V9Z7]9#]: ^?"cD)jK0uU:zX?z\>eDiHwTVZ]a_Y[\[]fhn¥utqu¡tpopßtr t sátât¢rárpģtƥvġsĠrţuǤxǥxŤwģuŤwǦ{ȧ|ťzȧɫȩ~ġyťzȫ~¤xpopmjhpswrmlmb]ZWTUTWZ[aadefffeeehjhc`[[XVX[]]]^acddaab^\\X~Qoo[yeuao~gxa|`x]zrs\iUhTkV{bMtXCqT?sYBuWDz\Kx^LuWGzYJw[NnTFzhs^cI8sYHr[bfgmkmii`fgkkikkfb]\YSTUxUrRfH]BkK/_>$K+@"H,O4O4N5N5P7P9R;R:S:R9T<V;X=^F%`E&bE'_B"bD#cD%cD&bC#a@!`? _>\:Z9W8Y9V4X7X7Z8\:bA&b?(fC+hH.eH+iK/qV8zc<fBjDoKpNjJcDmKwQ^bhosojhgnl£t£vr s¢us tÞsšvàtpplmmÝqpžqšsȥyǦwƤvƣvƤwǦwßqĤwƦyťyȩ}ɬǩ~Ǩ|Ģw¡uropproqpqvrmhhc[W\YYZY\cfdeghgedginplgc`_\WVW[^^``acbdeeabb][wu`{g{jygzhiYpS?uXFn^ivW@a8'nZyciYdVvWFlM8cC,b?+lL8rSBmN=pTAoR@Y:(tcycu[MgYx_ghlpqjicfpjmkmpoifa]ZVS~S{TpPjL`@^BhB,U7H,F'H)J-M3M4M4P9O8O8P:Q:P8S9[B"[C"^C$`C%cG'cG%dE&cD%cD#bA"bA!`?^=[:Z9Z:^= \?^A!a@%cE)jS0hN0jI0t\;kT5\=#^:"dB(oF1|Q<X?kJvQzRVYhptvĤyæz¦wtmœpěpĝpŠtŧyťw tâtģuƥxƥzȨ{ȩ{ťwũzåvßqmÞqġsƠtqátġsǣvơunÞqĢuát£sĥwĥy¦xŧyĢu trromhfmoppjkmi_ZZ^_`a^`cffjjkkihkmjliibea_[XWV[_bfffcceffec`^y|ckxaiW|YIbSlT{dk[gTgR|^NjXlVfVgWwZHqWAjM6dD,iK2kO:eE2rT@hK6uWHrVEo\n]r[zc|`ilrglcfmjlhnmokjee\XW~QT~SwRtNd@|X8mJ/a?"X;O1I+C'F,G-I1K3J2O9N:L5Q9WAU<X;\?!_B$dG(dG'eG'eE&fE%cB!bA bA \9\;Z9aB!dE%hM*fK+jM/rW8t^8kO0nM2uX9uV<iA/mC0jE/X@cKtQTY^ehnrvœužsoråwťxĢtʩ{Ģtqçwŧxäuäu¤sŧxʪ~ʨ}ǥxǦyŧxåvǧyȦznnƠsŠtǤwƤwɧ{ȥyƥxǦyßr tqnppqŸrŸrqnikljgmmpoppslc^abbeadggjonnopqrojghhfgc`[\[]^^_cfffdeebcdb`koWiw]v]rYlTuVcuVfpTbgpWtYz_z`JgShRv[B_B$iM1a@'iM6zcMuZKy]N}fv_kXqYedjokegnrqmmlmrlmlm_^YUTT~TvOlF]=tN4iD+`<"W:J/D)>$=%?&F1K5J5L6O;R?R:Q9T<Z@`C&bH)eI*eE)hF)gE'fC&dB"_<`=a= bA"kK-oL2rR8wY<~aCxW;jJ-Z3a7"sM5_DlPsTtPyO{R~X\^eimqsrjœqpplop¢sätĥvpoƤwĤvrqĢwģwƦzȨ|ƦxĤwŤvǦzȫ~ȬƧzɨzȨxǥxɧzţu˫~ɨ{¡sásãvàtojijlmmkg`ejllnrxvrnlecbeeedhlnsnsrttqpnkkhggffda]]__beeeeedcca`aa~lUjO;hUiWjX{^N_PoYlUeSsZsZs]jSgRhU~`NcQgUhUz\ItZCoX?nW>lU|hP~jU~gRmTqZmXvZxZ{]ktljmrkqhrumonoig`c^ZW|RxRpNfD]=yT9mI/jC+`<"S7H+;!<"@*F1D.E.E0N= L9N9P;Q:Y=`A#eI+cF(gD(hH*hH)gE(eB$cB iH)jL.nP3sU8rT8z]@gHpQ4nK-aCrQ3\DdIrTvSzU]][`dighggiq£urnlmmnàrrĤv£usnbjßrát rŸrs£uţvġuƤyǨ|Ǩ}ƨ}Ǩ|Ƥyǥxȥzʦ|Ȧ{Ǧz˫ťwƧxťxťxåwèyu¡rppookeehkmpu{wpliiihfffcegolqqvwupnlihijhdc`^^[]adegfefecbb`augRxxbvccRqO@cP~_MgE7gTjYfUoYnX`O`N|ddPZP{ZK}bPfWo_t_nZt`}gRhSgRpVrXlQlsuonlpnituzmlqqkjedb_YSxOqLkFf@y[7sX4lO-e@%^;Q3E,?&?(;%D.@,I5F3J6J5J0R9Y=aC%bF&eG)gG+hJ,iJ+hG'iG&iI)rT7y\>gI}`F{\?~eEtW8vV6x]7kDhHqNvRyW|X~X\\`bcenâxomjhqqrŸqkŸqmoĤuåtoruåu¢rlmorrsáuĞsàuţwĥxãxǤzǤxţwǦzȥ{ȧ{Ǧyɧ}ɩɩȩ~ƥ|ģyãyťxĢvàtɡwǣxåxuohfhjnos{urjilmjhefglnmpsqssomnljfadb_]\ZZ|V~V\c_ggedb`\^]sqYqYizbgOrQ9gMnPdv[fO~fNjQdMgPg~bwWD]NaMjThZjZ~ino]}`PjWz^JeSxZHw]koroqlppnv{uqnsqmjjgke\WSxNqJhD^>{Y9pQ1kH,b?"V8J2C+>(@+=(A,B,@*F2E.J1ZF"[? aA$cH)dI*iJ-jJ-iJ,mN1kN/vW<{^BkMfIwV:yU:xX<dFrNrPoQnMrN|XzVYZ[[cdcgfnopknnnp¡sƥxǧzŤvĥwooplifjŸqâsjmqĤuq p¡qqâu¡tģvƦxťxǧyǤyǥyǨzŤyƤ{ɩȨ˫ʩȦ~ǥ{ĢxƣyǥzʩȪƪ~Ħ{žvlimqu|wqplhikhehhhinmkmqqmilljgdd]Z{VvPnJbCfEkKqQtTYadb_cc]~Y|Z]tpthQxcfRpYiTgRoVuZAmO;rXB{dLnWfUeSiQwWDrP?dQ{_lntzao[p]zbkWbSnYx^ksrtrjosvvzpxvnonikijfa\XzQrNiH\@Z@uQ5nL/hH*[<R8L5E0>(:#?*<&<'A+I4P>T>Z;`>#fI+iL0jI.pP5oO4wW@}`GiNiMdGyR;~[?iHpToOXtQqRZ|SxT~XTWXY]_bdfffdbeddffl£rŤuƤwŧyŦxpqŸrÝqgehbjmpģtrmoosàuĢuŦxȨ|˦~áwƥxŤyŧ|ʫȨɪɭȪŦ|ãwĠvqàuávĥx¦wī}ūtopwynjhideede`cecffikoqqoikkic[YtUgMcGaElR^D{X?aHfJoO|Ya_^~^__y\wZwZ~v}jo[iVrVFbSpa}mz[KhJ2[9"lN;w^JiTvYG~^M^PcSdQp^~]Ova~gaTp_kxbiUwar\kXmXmWv^kpvvqvst||}ywolhjgllea_XU{QpLlHdC}Z:rS3hL)dE%^A$U;M6E,9!='1F4E3J9M8O8Z=eD+hI-iI-lK2sU=z`HfLnQvWeGjHtVqQ[\ZzT|T}WrOzVuRvSsOySV\WZ[ZVUVYZY``a_adf›pĜqävŧ{Ǥ{ŠuÞr snnrmiggkqpolldcenáwģwģvrģxʩɩɫȪĥxȩ{Ʀ}ĤwŸrhbeiqªwxê|zsfghhfgfe_[]^[]b`dgdddehhifee`}`_z[zXz[{[uVnPvVkQoTz]}az^|]zZadcswaopyfp]o[o\eB3cPx_JkY~gSx^IsXB}dOeRz^JqYmW]PzbineQzax_ImYw_|du^u\sZ}ahr}usyyzurwlfnilnmge\`\~QyRsNpKaA|X?nM2kK0eC)]A$R:K5H4I7E4F8 D1F0I.P7_@%hF/kK2pR5x]A|cHkMnO~[iJkLzU}X]b\Y}SzPuPvQ~T{T}WTSX~TVTWWT~Q{O~TVZ^[Y_b`abck¡sršqĚoàtĢwĠvrmqpljhmojhdcbdehnpÛqÞsĠxƢ{ŢxŤxȨ}ɩƨ}Ħ|ģzskajnjlnhijkgffgb`\|XxVoNlLqQwZ}]jh`bejqkkhlgec^```cg`]x\}^]wYuZ~`cz_~bvr]~hkn~iynyetilXmVAiM:eF4eSkZ}cPdRr^xdnx`q^nR;^>(fTkZdRm[vcm\nZ{dmtv{wtv}wsuokrrrmniecee]X|VvRpKfH|]@sW;lO3cI*YB"R:L6H3E0C.B+<!N4[B$fI0rR=yZC}bHkOhNpOsRoPxZ_baf\\~V|QoIyTVUXZX|S{T}TzOwPwQzQ}SyQ|S|T}SU]]\XcfjmkljjjĔlpšqŝtŸrpmpmo sndlg]U~RX[^finjiožqqprtĤ{ÞxĢywvpmqogc]^fda`]\vToOvTuUuVz[a|[`|`b~aceecmkicgsgihffhd~_}^~`ehc{_v\w]}djv}hp\gYml{h{qs^tcwfiWnO?yXI|\NwVH_Rj\tho{ioa`NlWydjYbSiYm]k[nZt_|hqoq|z~z~tottvqorkrlolha]~WwPqLjH~bDtY:jS2^F&P4K3D0G2@+@*N8 X@$aI/sXCeOnRmSrWkQgKsR]fddac\}VrMoKuPuR{UzPwQ}V~TT{Q{O~R|RxPzP|R~T~UVUWUX^\a`ccbcebcaellnmnqqptĤzvnklme_XUYcbbebabdijoßuv{Ů~zxtig^WY]`|XyV{X}[}Z}X{X}X\bdgchdfcgjjkgb~dfgggf~dijzax]fkighgfhjt~fRn_pk}jvgeSrXCoR@yiuclZpQAtSBsQ@tPA|\M{jj]soZ~h}bgqXoXr[zc|er[qXs\{ahkswuuwwyyqsvxtuojd\Y|T|SyNoJcDvZ;iM1_A'Q:F1A,G2L7R=$`E/pX?kPsWxY}\|\tUxXf]]fdf]WyQzStMTtMvPyNvLvOtJ|O{PyNWTyNzPzO|O}RW[WXU~RX^__\_`bbb`\Y\]fhkmœsœtuâx¢wĩzqs¤wth`ZVUY\`WRW\_inkkjnrjhh|\nQlQnTsWvYwZ{[`fdb`dfgnjiffbegjjigc}b}dif{`{`eklmpmpjegglgfy]v}gp~dulWy[EY7kp^|giTz^IyXEqS>y`u~hzfqdt[mXnXmVs\pZgSmWmWr\emr}|yxz}¦{vy}y{pmga_[WQzPkOaKtX@eH1X=%R: N7K3Q=!XF)fN:t[GmT~]ac~`haeaeca[YVyRyRVZtKmDoCf>jFsMsMmItMyPzSzStMrKtLtLyOV\^_][X[Z[ZXZ^cgaYVZ[bajlqonmtv¦xvvwurk`^]^hda[YWX^addg^YnOqTpSrVuWvWuYsXz]y]y_kqknospklihhlqj|cz`u[bfgica\chjosrpnjez`~ddzbvnWl]ros`uYInT@nS;gK3pr^jYcQbP~]LiYeTn\tejYvXF]Nx`fn\|epYhUdSbRkVmXlWx^jrw|uçũ§{{|rjba]\ZRzOrNfMv_EhT5XD(N6P;TA XD&XF+wgMx[_bgmkged^ZY~VWZ{UuQxQpMfAa;jAuOoKvMwNxPxOwNvPvPuOkEmIrMuNwPVXZ_b`]\_a`]X]b`]\~UWXVU|T]dinnn£uuâut¡v£vĦ{áwnbcennoidbfgjh]}UwVrWqSmQpTxZ`z^{^~_uY{ahlifgiilrj}bnomzcv[y^{`jdjic}\~^cdoqswvluhxav_wau_gjU}\Qo^n`{jzds\|h|gs`iYeVfVfVf[zWJsQCxWHoPAv`nZmklg~eMvXEgRoYjWeRhSvY_jszy©|ŦƬ¨~|xuoohhfe`YSSlKfNs_F^M2M2I.S>]J,n^Er^FoTeusllha^~ZuQWW|TyTrOnJ^<c@lFnGwL{QqIrHxL}Q{PxNuMuMsJtLvM{RWWUV|R|S\_`]XVWZ`^\[]]]]`\ZZZ[^[dhlppnkmlonung\`hlnnlkg`WsRnRqXwZuZnUv[`y_z`{apYq[w^w^h}c|a~cglqnnoxtdy^u\v]|_}cya|_az^{by_fknbfilqdt\w^s\t]okiWm[motau`cQwgrwakTgUiV~aNrP=Y4!dD1ybKx]ukx\zb~dr{cmXrXD~cOqT@y^J|_MpXx\doty~ã~īĬ~|}vrmroohf_[WTwPmSt^GW>&N2U=^K*hT8nRdoonrhhb^X|UZ_{TmKcApMiEtKU|PyN}PyNrHtJsJwNxNvNwNzO{Q~TVTVS[\STZ\^bb^\Z\\XZ_ac^b^a_ddfa^^fhjnlolmŸtpllgbjmoqka[WwRtSvYa~]w[tW_tZu\v\pWoVv]{ag~d}bsZu[yafhknphz`w_y`egrXkSeKtXgik}cif~dg~`y_wktZ{\|^{_q|evbw`x`zg{gwbzg}kz~gv[lTs[nWdN}eKvdIpZCu`~bljhQhu}blVeUdPv\FuZDvXF~fPvYy]}dr¦ũ§æèåxvsmqlnjb^\\UxTmRhY<[M)YI#eX5|qP}\bhmqrkg^[~W[]~VuNeAoKzR{S|U|S{SjDoIoGoGsKqJqIuLxPzR{TXW~W~U}T}TZ_`\WUS]ehghdda]Z]acaadec`c`\\]\X`hmpommmnokkjihg}VnPdLjM{\|`uW{\sVuXjMiQnTmTmVrXx\z\jjvYmQvWtXt[zblji~ex`~dfddaoQfL~`hqypb}amTvZb}boUgx`pWpXs\wzeicmWyd|ejrppo[fShVjYn[zho\n[x^M|^On[~hploo}flWlY{_LwZFrVAuZEy_IlVw]fw«î}Īƫ¨¨§ħ}|vqlvmkkjhe^W|V{pTmfIk^?]J)\H,tbLv_mqh``_`|U{U\W{QsKvLVUSzQvRwNqHjEnFmFkDlEnGsJvMzR}TV}U~VVT{R}TVX]d_ZV[]bfeeghe`baegdggd`dbbfiffbcefefgikprof_X]wWmUqVrVnWtWvYmTpUy\sYoVsXtZv[u[sYr~cnU|ZElTtYtZqWdd~d~cdddcdb{\nQzZ~`otn{\u[rW}^|^}bef~fkUgRkU{dMlX|ezci|hxboZlt^hS_MdR_NhWxWIl_zgp[dRlW|ekyax^jt_mXoYdRdSsXFuZHz_MfVq^kqªyé¨ëëç~zxvrrlqsomf_\}ZpgH`O._J'cJ/oZBtVe`_\Z[|VwS[YwQuNzNWTxN|Q{OvNiCj@rHoEoGrJwNyN{OwNxN|Q}STSUVTURVZa_d^]XW\]`cahb_^^``aa_e_ddd_bcd_^afadddifgb_xXwYtXtYtXsXmTrVpToVjRpVv[u[nUlTrXtZzb~u]r\}[D{WDoZtZv^pZu]db{av]y_hke|`pWpWkRoWr[w^cpYlUy^x[v]oVz^hsqWjS~xmSw_e|dtb{enZk[kY`O_N_KhSjSpYmWxbf}bu]hNgNlVfPkUhSqYfQ|`OhVhWx\MpSBqWDt[Jn]jmx¯èĨ§ĩëzzw£y¢y xt£wtqrnjcc`{tQeZ8^J)pa<xmI~nKrLyU[[YuPrPvR|UzTrP{SUU}QwOvMsMqJpHX2jBpIrKpItLzO{OxMyN}P~QSXTS|R}QzO}QRWZa_`_\TW[SRZ\\aW]VXUVW_bijgdegbha`_`bajggb}_|[wXnUqWqToTjPmRnSqny^q[qXpWbNmUt[gyu[qYx^s\t]oYt\pYr]yarXt\p[ejk|f~dpZkWdP|XG{WEmXg{d|f}bvZmWiUsXxaltZy`vmxafgyct_yas_ud|glZcWkYjUpNA[8+kYdTp^lWq\gPhPgPlVz^JsWCeOy_JjO;nU?u\HqVDjP:nY@qZHgS|cr|ƯīīŬê}ħħváxĤ{¢yĤ{Ĥ{Ŧ}vtrolfb_ZwhMq^DuhFmZ<v_F}hIqSuTtPnKsRrO~UV{U~TYUTzN{NyMkEtLtJkBmEtJuKsLsLtLyM{N{P}PQRSTVU~S~R~RsIrJxMTZ]_`\WZYYS~PV[fc``][`dejebddddeba`a]aecd^|\oO|iG~_GkSrSkP{ZFiOpWx_fv[nWpXsYmSw\z_ckjrW}`}`uZpWiTjTpVmSgQqYx`jmppukkx]fQt_v\j{gl{bv[mUhRoSwZ|`hz`toqpjxbq[fRiS{elsjY`LlxbM~eSq]gXyYKzZImJ:{]JrVePeTgSx`Kx_HoW?mW?kR;oT?kR<hP8iO8mN<dQ{equů~ƭªū§|rwvĢyuĤ{Ť}w¡xvtpojgebygMq]@gN3lT:ybIqTyWxVzV}VWZZXXZYTQuI{NyLoGxNuMuKrJmFpHrJvMwNvJ|O~QRRRV[YZYVWVVVPW[Z\\[[[[\WOPSY_c`cedeifa`acgdhijljmlg^vPy_BtT;u\<z]CvS@eJfMbNkRoSrYx^z^u[pUy]rTcz`}bfmnWmUpZoZbLcNdOkRlRnWsYv[nsurswstW}aya~ch}f|d}_nUeNkRoVuYqWpYpxlowo[l\}`P|\Mn_lwdhVieitZjShTlRjRv\HjSt[u_nZy^MtYIv[InS@mR>jN:fI3fH3bB,`A,qQCeXxfrzõ{wu|¨Ȱŭ¦{jrv¡xŦ}ã|¡zâyĠxsrrqkjhgwUj\=xgH|[~Y^][Z}UY~W\b`XXU~P|PxNyOtLyQtOwQrLnIoHzRxOxM{LUWZW[\[__``^]]^X]\^^^[^^^]adgcehmģvqomkhfb`ac^ffrpqqrpewQ{cDrT=pM:|^DdNjXt[wZjUfNeOdNpYt\nXw_epWoYmXmWu]oZaP}]J_LdR_L`KwWCfRgQw]t\q[z`lk{eiq~ooUw_sZxZy^v^fkt^]M~`LdSiXpZs\up}gpZjSdXuSH{^O|]O~ZQ{[N{\N{ZPvXJrR@sQC{]N}aQdTeRzaLfQt]x`~dtZ}hR~hS}gPkp]^F*[="fK1iR8gP7nS@cSu_ow³Ż}vuxy~êêttmw¤y¥|¡xţ{Ť|ây¡vorrkjhduTlY<u`CnX;mPxX{\{[{X|Y|X]^a\V~R~QwLzOzPxMnHtOuNxPxPtMuLqIyP~TWZ[^bbecfee`fg—i˜ibbbdbšhgdbaef™klkiileÝqœqpnolkkjgegglpspmktX}`I{`JdPeN{^}clgthrX_N^LbNgTp]{fjlfRdOpYybfg}eLx]F|`h~gr]pZpZcOhUnZnWr\p\t`yd}jlr]eR~_KePjTqZx`gljhVmUnWr^zd|ayc~^RfV~f|h}lu]p[pPAsWDy^IeTvcmR;~^N}_PeSkZiYr`aS|_QhX~dTrXFoT@tXE|bRuao]EgO4W< jR9nYBnY@nT?~cNw^ho~ö÷Ķywtwxzz{xwuqvyŧ}x¡xĢ{Ģ{âyqnjljgd[lNlSkRqVxYzZ~^yW{VZ]YZ~U{R}R}Q{PxNzPsJrIrKqJvM{R}T}S{OtJVYZ\_eh™kgg˜jšlŝpŝqĚnijll—mÚmÜmÝnÞnjšjjžnšligğpjÚlkßqŢuğtojn q¢r¡ronpsqpppi`rWy\_`t\tW}\kyaʽpnt\nYmVu\u]|ejr}cv[cdf{\zYy^Gg|siabt]ez]nWmUpUw_{bdemYybiRw[E}^IlVr\v^}d{a~bdbhg~fzzaNqKA|UJ{fzhygljWuXFaF/{eNu`zaoWpZsZ{dLgSy^tYq\{bOiL:gN:dH4jM;fI;hJ=mRDlTAZ<)mQAz`St[IhJ:mPAjVo]|hu|±ŷĵzwts{}zwwwv¥xŦ}u¡w¡x¡zĤ|váxsrpmjge_pSkRsXnRnQpQpTyY]^YZ|V|VzTvOtN|RvNpKg>nEqLrNwO{S~VWVSV[\cdgjiiĜoÛnŜnÚmŜqŞpǠrÜpšt̩|ȦwĢršqĠpŠqšqmglßoŠsĠrĠqşrÝoĠp rƣwǥyŦxĥwsop¢uħz¤xtvpuvrlghgfkt]uX|]pXwapuv{dkqoYpXqYs[t[dcz`}dfl}bnX`Ko\ytgflloiu\tYw\w`x^{]zas[cgsYsitZt[tWw[w_jeiu\dOeimUgovcwem{kl\qVF~gV{`S}_SfYteo^s]}]KkVvX{\sZdO9tXp_Hq]I~iTiV}fS~kV}hUj[hXn\oRClPAx]KkV~gr|ĵ´|vvw|}tysvxsƭ¨~ystvãyuâztupnjcbazVmTwaKx`IwaHpUyY~\{[y[pN{TzTuOuMiB{QSuKf>tJuKwMyOxOwPzSVVTXXaedbh™l›nĞpƟrjĝoơtȦxʨ{ɨzǤuǣtʩzĥsãrÞpƢsġqšrmmlÛooƣuȡu›párƥvȦyȨ{Ǩzƥxãvãv tĥyĦ|ĥ||vvpouqkf~etir^nUpTkSenmrhgsq[jViV\OhSqYr[sZqYr]|eyb|gfTl\waybm[zery~ju^r]tauas^s^r^s_r^dhlywaw^nSgTlZjfiWv[Ju^Iwffzejevao`taltd}aSxYHrRB|[PhXsaral[dT~ZJeSeUwYMwaMw`t_RqcyooZkYp\kWpYw_KuWDfPu[gyssw{wsxyzx¦|ĩū¢v vtvģzĥ{yxoljf`[|VoUjRvaH{cK}eMqUvVrSjIwRuMkFkGnHwLT~QmDlCtIwM|R|Q}QyO{PTTXWWV[beffjßnǢsƠqŠpŝoŠqşrơtğpnmĤsţtiƝqơrȤvǥxǧyɧzƣvɩ{ʪ|ǤxɦyƤwɨzȥxǥzǥxȨ{Ȩǩȩ}Ȩ}styysvsoh~e{bv`u_t_wdd|`aMsYcjl|e{bhhR]LaScTq[jTmYnWy\w^t\aft]|adu\s]qU{`s_r^r[oYsbvas\lYt_gxar]eu^~g}tw^sZz_JlUsZ~gTgVvaqWHytqavas]p_s`~ho\fWn[jTdVyZLoZmXlXlVfSoZxUBtPAn]vgsvykgUeY{aTp^yeo^w[JvYD}dNlVewwux}{zywy{x}ħywvuv vwusrhb^YzU}cJr[BoZ>waFjQlS|hLzbCiJjHa?hEgEuNVxPpKkDpItL}S{QyPST|O|PSXec^^hfhjÚmšlěmƞpǠsǠuʥwĝolÝnşpĞoĠpŢqƢtǢuŠqơtǢvɦyɨ{ƥxʩ|ȦyȦyɧzȦyȣwŤvţvƥxƧyŤyũ|Ŭ}ttruxurnqnk{`_KjTu]wbnrW~^JoX}`{duaiToZkVt]x^rZsZw\vZy]pW}`v\u]~ctZu^qdfe}atWoWu[qWlVpYoVlUiRphwZlSpWv`r]ppr\qVkRkVsWpVjWr]s]zu`gRfQhXgRpYvXFmox_}bR}`Or[~dPcPhSs_khw`vns~xrbm]iZj\gYwfk[uWGx^JeTjT~bt}{||{xx|zrwĦ{tttntsvrkf_]UxcChQ4q\>saDr^At]BmN6uY>z_AaFgImMtRUxPwPlFtKuMwNR}RtL|RUUW\_cfeffghj˜lÛoȡvƠsƞrÚlÜlğoơrŠoơqŢqĢrƥtƣtǢuơuǠuȣxȤxǥyɧ}˩~˩~ʧ{ǣwȧyơvĠsƤxȦyȨ}ǧ|Ŧzsåxsnjgijgdei{`nWuZrYjWkQmPmVqY^jrZdPhPoUu]y_y_{`w]uX|_tZq\p[vas`q_q_yg}gol~fdKmWlWlWjV^NhUmYiUhTmXrYpXjRu]t]qhyfoVmVhXoYjTeRnYw^j}lPnQooqYy~gs]|`Rl^xg~gs_t`mYmnl{hp~oku_ycs\kZeWparVFzaPjXfUjR}_p|~x­~{zwsw}vxxrtqqrorljidZ|QuMfR1^D$cI*dJ+eI-oS7dGiKiKnNuRyR~TkFlEqJxOvNwN}R}PyN|O}Rb]gdgjefjkmmmžnƢsǣsȤvȣuƣrƤtǦuǦvɧyȧxǧwǧwʪzʪ|̫̫̮ɩ}˪~ʫ}ȧzȢw̦zʨ{ǧy̫~ʨ~˩ɨ|ȧ{ƥ{äxäxuvroiopgdim{`s\v]pYbOxXDmS{bdRdMiPdOhT~ey_s]u`yc|du]`IqYz^e}csZ{dillwahk|fgkM9yZFoXq\fVn[t]kVdSjW}dgfQkS~brpxhr[q[mVmZcRfUhVtubJ|bKgQ_MkUs]n{eRwVI~]Sm]kskuct`l{grbyoҕqybnX{`NiUkXm\iX|bSrVH|aQgU~fSu^J{_}`zyxðztqmqoqy©}xons¢vvrnkkgcWTkNcP.W=]= jO6zdHhImLvPzSwS|TwOlHxPyRwPxPyO|R|OyLzLR]\cbbeehimokÞnĞpŠsǣtštşqǣtǣuơuǥwɥx˧{ɨ{ʨ|ȥxʩ|̬̭̰ˬ˪}ɨ{ȩ|ȧzί̮˫~̫ɩ{Ȩ{ƣvȤwĠvŤzĤxvzyvsqnmi}`s[tS@zVEwVCdQlYdiP~`PzZIpUAjQ>fTjXva}fycu_u^nWgStU=lTlUw[w\t[}cglk|ay\w^fieM5tX@oUv_hWgSt\oZiWiUy_rYrSAuSBgQyb}g~fw[oVmXnVpYoYcQv^yu`wYIyUF]QgZudzel\rP>iUze}byZyagjkqj~h|{hvmq}iy[JgU|_NbUz]NwZKuUIk[}gTvaLtZInTex|wtpknrskxvtpÞvģytrqqmf`b\YsbAlY8r\=s_@ydFjLsOSV|UzPrMuNXXzR}RS~R}P|PR}PY`dcdccfghlnl™nǢuŠsǡuţtƣuġsʨzǥvȣvǥwǥx̩|̨|˦zʤxȟuǟsşrʦxʨ{Ǥwʧyɧyȧxȧzɧ{ɩ|ťxǡwǥ{Ǧ}Ť{Ĥ|y}~§rerYoSmSfMx_`}au_|_rr^vWErZCw_JycNnXu_}f~ez_w]aJiQiPr[r]o\dS`ShUiVwdwdf|bpZlXz_nWz^MgSp[yf~ju~jjViToYp[qYiTnYw`{ezes\gRuSDkWs[o\cQgzgrYjUfRwb~gt_ePmW|q~aLx[E{cNlViij}ftk~kwjv_gnXy]ImR=aE.`E.nTAz`PkXw_IcG2v\FmQ{bnt~¯snoqpsqstosãy£zsrrpolgd_[xWwcFs`Au_A~fMpRzTZ~Y~X{SwP}V[uO}T{P|Q{Q|P`Z[X_^bfcadghigfiÝnĞoÞpnşoœmàpŢršqǣtƣtȥvȤvǢsĞpɣt˦wštƤuƥuţsĥtǤuơrhĞolţvƤyţyģzĤ|Ĩ¨í°|pkx_tZy\uXkOu[hnjmwqjZy_L}`Ny[J~dPpZw`doUeMcNmRsVnVjt]{er]dTlZq_dTjWzctaq`knlm[hV~gmts}bMfRnX~ceuXy^z`{a{at]t]lXp[x`ydjyak~evrggQkUpzfx[GpTArUCmQ>{^LlZoyyjk|_lkjz_|bKrQBkM;bF*\<&aA0v\Ms[HiO9qVCv^HzcKrZiin®ĬŰ°~zslwx~~wtotvwvpàxutrlhia]]mQvVCsS@lOyUa\[Z~X|T[|VqMzRU}SyO~R^a_[WYX[]dfcdbdhmœmžnÝnlßmĠnšqÞnǣsşpƝoǞoǞoƝošişoǤtšpkàoţsģrġoǣsǦu£pƧxǩzǫ|Ǭǭūɭƫzqn|ajWoYnZdR`Nxak|cr_zntvbw^hTx]K|bNx\IiWfS^LcQq[kUpXqZhQjzswbmmo]v`mgzbtruu^oXo\v_walVpZkXgUrZoWwYGs\w]rZsZv[y]x`z`}digzbnslmY`>)_Jq^mvxdy[hS:lO9zeJ~ajny`{diUlU|^jOiS{]IjT|c~_MqXAoV?hM8lV<iS<jO;xaMu`JsWBgR{aho|íųƲ¯ôrrprlytw}yrqmsuts¡xwunpmhb__}YeHhMuTzV}[`}Z^\ZYwOsO~VZW~RVa_\XSRXX[_^ebeeggjij™kjÛkliÚkĜllŠqĚmƝoŞoĚmęlffěliğoƠqşpƢsƤu¡sǨ{Ȩƥ~â}ģ}{{|xqjdh~gvd~es[u\`hr\mw_io}cs\iV|cOx^JhVoZw^u_ycx`hT{]IwZHoZkm~lu||khrkXq_movd{c}fn`dSePyYJn]v`s`o[pWkVkUoVnXnWtYy^yaybs]r]lWqW~xyhydLt^{cnwmWw`iw]G}fPgip{g`MhQz[GqN>}[HlVoZaLiTqweOlWhS~gQgO4hL4rZEwaLt^FqXAydLtWbp~ŲŲƴűòymttskyqxvvsrrsrrupoqpllgb__XuQnPqS}^~_aib[yRrO{TV_YW^d\UzJwJ|MWZ[acdfadgegfijg™kiiÚkÜkÝjj™i™h™gØi˜h˜hefžkànƤrǥuǩzȨzŤvŤxŢzŤ|àzuwyzusrspknphnSs[d{hSqK9dOt`}hzduap]sZhQlWu]hjyad}`iSiRw_q_salzfwc{km~ik]m^nxgvet`}fwa{bm|bKs^jSlVz_`sXePfPjRoWnWpZzcNoYpZhUr]mgksot_~isUDxcox}kx]KfSbOiTdNzYE{\FrUb}cmmhSz_{cdTrUCoUAsZGqUCrXDv`Ku]JiR}brĴ²²yvwunkunropqt¢wwuqinkjkklgbaayU}bHlSyYgmgc}WrLzUYZ\]W[ZXRc@mISV\\_``_ccdcb^cfgedffebeřj`ee™iefÜkmŢrƤvƢuǦxäuȯȮʯɲĪ~¤y¢xtspqsqmnsriaIr[elpZv_ppZu_tXrYt\~av[lWv_|[nmTz[|ZsZnTxaxb~gxd{gvf|jon\l_}{jj^hW~fgzbs\dnUoSA}d{jkVjS{eNoWnUu^Gt\zlzcglv`q\wuzezmybe}fz^Mhh|shhNcGqSddfvZy^HljUkYnUu^IsUGsXEuaKvaLm[BlZAw\k{ŵ}}ztmuurwssssutrroodhmxqfe`~YiNpUa}^~ay\sVqQvQ[[VX][ZXT|MwKX[Z]`[]`aaacc`ba`_aaa^`[c^edffnl˜jğpŢtƢvǢxǥ{Ǧ{pvsstjgb\kovit^z]heVaMgTfUs]ybxbkv}csXkOhNt[|^y\r[w_ey_IkThR{`JmXhSvamY|hyflub~]Qufmzgyf|on^lYn^eUn^o`ubjVxWKo[pu^{eckRgUgVuZJ}hvrtppmsnhjUhU{dM}`qtjf~fu`rZnWxaferXy[lQqYyTCu_y`nX{\L{bQubLoYEbL3jR9kUe{z}|yltpvvuwwzuptvtrqrpxutjdb\yWrS|jMiOrYnTjOgIvP~W~WUX[\YVwIXbkh^\X\]]]`aa_^Z[]_^\][\Y\dbbcßohfjŦvǪzʪ}ʭǬƪ~èztfa`^^aipm`w]mUkQeUn_p{cm[qZgTq^mk}gqZbL~^JhTv\ep[nXoSoX~dsWuXC~cPnXmWeSnXtap~fSbUmqn{ngWrUJpOGsXH~t}cT}`PcUwfkr\fefp\saze~jmmuqydk{awiMimXt\owhm]mYs_}js`}fiSjVbOeLvYlUu^u]w`}ho\x]Mx\Ov^JjP:aE-jU:{hOy`tr}wxssuvxxuxxtrrrsw{rw|vslca_}Zp[>zdHzcGzbHy^G]HjLyTxU~WUX[ZVU^eombZVYXYZ[]\\YVVV\XY[Y[[[Z^bdgbegnmġsƢwáuŦyäypwpkmmjimonkb^tXpYkqnxyrVlWnY{auzeuZpVnY~fm|ehP}]o~eltZx`t[oXdQrXjzsy`OwYKgnnnwuiuWNiIAbF6yaHmk}du^gQx_t^wau]w^qklkvb{hnnp[izhz]P`SuTFwehWo]vclYiwZiPkUx]}_}ek}`NyqiXqTCkM;nUAkT>cJ1hW;xkOt[int~wssu}yxvuqtpuwswy{xyzvmcghdqTs_EpY;qV9rU8vZ>cKoQtRyW[^ZZYW]b`e`[ZTUWYYZ[[XWUTVY\[[[ZZ[Z^^``_cemšvŠuơxŞynmqnqzyxxvvtpnfx^pQa{egiu}es\ClN?w[E{`fax[w^w{_dpppkh}bz`lXwbtyhfZ~`SiJ<p_pwip_reXx[Tt\Pt\J~e~h~i{d~gvau_K|_u^|bRnX}bwan^|kZ|gVu`nk|kzflhQv^hk{dpYqZtZjQiOoWp[ei}gyYJsaqZrUDwXJtYGw`KkR>dJ0kV=u`InU}^e{zyvtpu{yxuvsxxxvxtwuutojjejguZrZ@lQ7lN3pW<|`GkRtT{Y^b_]]XV\__ZZ~V~SS~UUVWYXXUVWXZZ\\ZYZYWVY`ccdhmkkoÞržtqqqtwvvz}|{uvfhQbLgOnYiWkUhTsavahRdE2oXfg}y\x]gqslWrnq|gxazavaoavyzlocUhVliZo\tdx[Q}poo_}eVgZq^o]LjZz_u^p\MudOq|mZmYK`L=ygU~fgk\l_ramVfpk{c_NcReP}aLyWGzYHnWw\x^q[jVfRlP;w]Iz`NuZIsYGjO7aF.lS<qXBz_JnUy\y}~uwuwqw|{{}{vsswy{wrssnifiiff|]u_FhL3kM6pZCkPzZ^^accaaX_\XWzMzOxOwPyP{Q}TSU~RXXU_ZY[__\ZYYX[_bcfgfgeecghntrslhly¨~{z{qmftZ{bMtVAeH3oS>{]IxhokmXfJ:pW{_waoZhivannjnlkujt^|_MnXdwemy{wvd}l~j^qSNulrnZkQFoVIp_Io`K~iZ|jXtfRhUF|gWvffVDdS=j[E|q\|lYxdU~iXv_iUn[fqp\{\JjJ8uWB}`M|^KxZHqW~_rZ}`M|_Lz]JsYE{dPpUAoUCrYEgN8fL5eL3hO5pX@}cM}[hyvtqyv{||«wxwsuswqkgkkgfbegb~[m[?iP7mU?~mP~^|Z\ccgid][\U}P}QwNxMvMvNxNzO{R~SSTWYWWZ^[\^]\]a__adfefgefgdddebb`epxwwvvqjjz_iQtYBt\Br[A|bKfojlq]z`y^s[dQjXp}rr[y`z`w^niwhs\{e|_Iwaq`p||~{zqrgrWOh~h}msoqvcgS@l[CucNyeT{dlwevcwexeWs_PlWE^R9dUDmZLpaN{lWo[bPj{ra{eqYBvVCcPdQnM6pQ:}cLvZEpP<x\JrVDnT>~eQ\;,v]Lu_KqUEt\IjQ7aI*iS5sYAy\iuwyzvtwz}~«Ĭuvvqqqtsiidhmfehjicw\EqU=}eMx^y\_aclplf\WV}RsKwMwMsKsLuLvMyOzOSQVUWWX[\\^___[[\aeijkikkjnŜrmldjmnptuowuqdhs^fQeQbQlTtZs\llo~kybqYmXlVy`n~t|`y^w^hson}cvu|fzuourvno{zpempzhkZuttcziYzkxjVwdxskl~ob~rcyj[ym\xo\l\LbN<lbNmTw\fqstpWsYv^p\{^Ju\EiR{_L{_N|bO|gRjR=_F/w^Jxcv[Iv\MuaPvaMhR8hT6gQ7nWju|uytttty~¬ìyxvvrroqmjllonjllkfgQ|eMoR|jQlVtZ_evrmbYS|QxOrJuLuLuMuMwPzQ{P}QRTVWXY[]]_`a`]abadgghigfinqŸuàvpnoosrsqwxrih{bfXm^jYm\gU{fizdpo[fUjZxalsZr\|cnvmr\gRxcr~wfq[o}m|v|myeSlawg}ky|rqgrwpblavfsvjoq~ma{k\wetf~h^qdxj}nyi~oxqzi[{k\nYs\v^v^ju^|ifSiG>oO@rYfThW~f{amR>jToYnZ~jX{hVtdPrbJhR9eP3_J+~kNfsut~urwxzx}{|}¨}vzxrqoqqopnonmhrWkQv_GpW>|`KsW_oyqi_Y}QrLuKyOsMrLvMqJwO{Q~RUVWZX\]\_aacbbdc^ce“gehhjolnÞsuãzĢzâwtvyzxsmccaky`pWu^xbw_y_eShWmWsXL}`UkTsXkWlXhSyaonXhTeVjWox~aUw`qh]{p~tti{m^qyi}mplbyhyjv}jyipfwiz~r|oqwbwh[veVzkXwfW|h]{j[whykykuzw|pjV|ff~_Rg\~h{xv^lXtYE~gpiknmVrZDv[HtWJu^Mi~nZr`Mn[DeQ7p\CiT=xeL}^px~~u|zust~w}{¯ɶì¬íy{utyxvrmjnnpk{`mUu_KsZBiQ}Yd{wrg]RxPtMvMsMkFnGvNxP|QSRVZ\[]\\^`bcabe^adg™jhfehjnffeÙpĖq–prßw¡y|||yrrjmnnrZdclY|gUzdR|bSv[P|eVr_nUHuYLfO8zaN{aTs_kWw\Ju_hVmXw`{cw_oy|yi`GJskqmtknzxqwh}pbzn`pfvjyw~svhk|mthqrf|nbuiYseXuj[qgUsbTrcTwp\ubtcZ}peth|pspq{m~hUs`v`s^~jtcziwbmX|ikqsxb{ev_z^LoYt[GcA4w`L{bzgVr\JmWBjU?mYAo[Cp^FtYjv{}}zyyvzr{xíƱĬĬūyw{¦}}~|ywqruoiz_{^pR{eMfOsYryzocYR}RyOuKrIpIrJtL|PRWX[]_]\`_abcdcc™i˜jÛlhhgjgižpnnnÙnšonl—prz|¦~}ħ{styun|ftct_kY|aWs^NlSBpSD|\QpamxaaG7_C.oU}jVq^hUeTs\t`r_lleRv^zuye^xyynrzushvkauiwkawga~mf~rypxoxkyj}qxo~laqfxkbypdzrfwocpiZkaQtfXth\yr`yoah[Srf_yje{mixlh}vj{thzmeqzdj{coegTrmqxdoybs^sduev`xctap]}iTwdOq`Kq]JvcNnYDnZEm[DjV>fQ8|eNev}~{ztsvqv}~©ƯǮŭƯz{¨|xz}ĩ©~}|vsmy^y[sQubI{_Mx\|yrpe[S~QxNwNtLvMxP|PVTZ[\^]a`™ed˜g˜feeg™jělÜpœmiƜmǜqǞpşpœoğr ql sâvŦxâuvsuz|§~ztw{tqfax`xcq^n\EdL1cJ/pT>txw]KuZG}iQ|fSt`v`eTjuvb{bRp`xn|k]{mkxl`vi}qzlxoesi`qg_zrg{ri|qju}yqul}qh}qiri~pete^sg_|uhw}wiqg]ncVtjZynawjbsi^i^We^Wqoh|x{{wuynyrquivze}et]hQdTpnj_v^lya|e{ftcjlp^o\mY|fRmWBpV@gM8oYDuaMdR:iT;eQ4dP7`rvu}ytvsoz¬ůƯƱDZëê~zz{ê«wnfz]qVwdKzZ|}}{o^[S}RzPzQzPzQ|R~SXZ]]]__bdcÙgÚh™fÙg™hšiÛkĝmmÜmÙkğpƝrɞsƠsǠtǡvǣwŢuŠwĢzĤzĢwĢyâzå|ǫũ§|~|qlild|kVq^xdP_I0[@'V; gI7u_Kwan\}aRpVpYjY{bmq_{e{}t{lrxo{b`naqcuc^uf`obZrh`shbticseayrhzmyrss~wn|pjvodqf_tf_uiti^rg`|p}n}yle_UhaWwwkw~nixjvp{ut~}{t|kvdqTAu^t^wgqJ@kXs[x_zalVvdxa}esiTiVkV|fQu`KpVBsWErYGlWBhU<dN4dO6iU<vY|dsx}||zzsqzíŭDZȳīĩŪħ~w{ħīêwliz\{kOrRqxof]YU}RzR|R~SUYX[^aba`addÕdĚhĞjěhÚgÞjğkğkĞmĠnĠnƠqşqȟsǝqǞsşrƤxɧ~ʩɨǥ}ǣ|Ǧǩĩǭȭ}{¥ª~zy|w{bj[DcV@fV>bN9[G0W=(Y>)^G/dL:{fR}exaSq\ott_}ivb|fn|dRur~md}imvg^od[tldrhatjeunhsffxpi}upugkxjhuifrcetfiskgridkfapofpmbztn|}}}vt||Җpsz{~up`~dRt]z_OwWKgTr]jUt^t]s^{^v`kr{amYiT|cMz_Nx]MiYxbRlWBdO5eQ7eS:kV@tbKoWpty{yzyyws~«ǯêŭŮūƭƱŮƫĪůpsl}_vYcvn^^XSSU~T}QUXZ]__cebbcdÖdŚhÛhĜjŝjŝkŞlğmŠlœkĠnŤqŢpǢsȣuʥwɧzɪ|ɬʮ˯ʰʰˮʬȮȰĨ{ǯë§}|{^^?,]@.\H/\D,\D1aM9\C.]G/`M6oXGy_nlY}dQqYq\kip^sb}lxpsboSKktuxxi|k~l~{j{rf}oxmfwlhtnhsoiyqpxnrnlju{wt{xt{yu}||y{֡ˊnllrsw~cQoT?qSByenw`vamZu_zcydo^fUzdubl[m[cRiY`TbSn]kZr^IcK5xgQscLq\GnYD{fNiqxvtv}|{¬ŮƮìëĬůȲƮȱrxuohd|umef`[VUUXXVZV[``bbgdi›hšhÝjiŜjÝiǡnŠnƢqšoɨuƤqǣqƤqȨvɧyȧxʫ}̬̮̰ȮͳαͲͱͱʬƨŧ|ĩŨy~ciUE_G2^F3`O;aQ?cQB^L>]H<iWJt_T}g[w^mkgu\lXey^kZ~iR|dQzaUtbyhw`Ys`Sxh^xjeykctg`rm|rk{sj|umyxnyupyspstnzz~|}ϯӢ͚͆xwɩ˨˄sjlri|nxezvan[ucu`o~h}g}clVqZkViUkWo[wdv^qyds^p\}dT|dTq\Ip]HmXClVAmZBt_Ju_rut|wz{ªůz}ŬĪĩűɵƲ®|yvpwzwrlkfb__W]^_\\^_``bafiĜkĜkÜiğkĜhœiŠlơoǣqɥsȥtʩxǦuǥuȦxȨzȨzʪ~̭ͮˮ˭ǧ}ȩ̭Ͳ̰̮ǪĤŦƩŦæ®pbav^maKk]JP@-^Q<cWFl^Rwj[~qasb|mY}dmzjpZuZpZuaNn^Fm\Fp]MtlVuq[n`SpaWvlbqnboh_ogboqhw}rwuz{ƥӯۢРԉyvԎstlkrloxfwuy}aP{hogpsYkRy\grVgPhPqX{cphlU@r[eX|_Rwb{iUt_JiS<hR:hT:q^GgVnrrxsuªízz{yħ}İɹǶȵĶ|xzvspmjjgda^\``^`a^\`ddjÞniǟnĜjŝlißkƢoɤsǣrɥtɦvȥtƥt˪z̫}Ȭ|ɭ~ǫ}̯̰ɭʯɭ̭̭ɬȨɨȬ˭ɫƧǧƪȰq}leodRWJ6^S?VK4\M;^QCshZjv`udMs[ivuxchZq[RdSI[J9j_IcWC]Q?ZV@hcQmcYg_ViaZ]RPa\[U^[su|q֕őǎŨԬڧԛNJ~‰{usskqyuvtjZqaqq]iYxds{g`QiWu^jWr_s^jTsU@nY|gl}`Qz`QuawWMnH@eWmXr\GmS=fK1dN2lY@x`L}enjpqu|z|tou{¬IJijǷĵzzsvsojkiijheaa`a^b_`dc`ehÙlŜl™gƞmşmĠlƢoǤqʧwǥsťqɨuƦqɩv˭{ͲͶͲ˳̵̰ͳ˳ʲ˱˯ɭ˯ͰϲͲ̲ʱȲëvmmnnVd`JOF/NA-D6$H<,k^U|piZOE>1"7%E7&OD2bUCdUCYI:@8+65&41!FB-LJ6@;.<6,C;3G;9KFCC@@;<<X\[I\\}xןǤΐĖĘĘÙşʥΡ͐~̖wrknuxivhRiu_wv^nYlVjT\N|djp\iWsu_saiybp[oTAgk~g|cOmP?nTApTBnR?bH0dN4nXBqXDzfTzbiopu}xxnks|zxqrmjeheghhhgaa`c_`b`dcjškělěkǞnȠoǣqĠnȣqɧtǥrʦvʧvȥrȦrȦuͫz˪zʬ|ϰ̯̮̮̯Ͳ̱̰ˮ˯ɮββ̱˱˲DZŭ®~|z{s_\W:UK2I8&7)9,3'2(2(#,!-%/%+"'!(!/( 0) .*!.*2- 3/ 1-4030!40%83+;529636<8MXUEYU÷Ʋ埸ȣȤȣĜċśǞʙƦҜLJÝէ{npyyӹ͏~ye~eUv`s\xcodiTfQgL|_ItUg|^u[vyafRt\mVp\~`Ozcu^mZhVmVlVmYn]tbNgQ<eL5bH4u_Nv]Kzfjmqxvrmjtxxytvzyzzrmjfe]__bac_]ZY\aacegi›mŚměmʢsˢuʢtȣsɣtɣuǣsɤuȣsǢrŝmǡpɥuˤwˣwǥuʧzȧz̭̯Ͱʭ˰˯˱̳̳ϴ̰Ͳ˱ȭīĮîvgvhUf[FPF-E8#?78/0(-$)!)")$)$## %"'$)''&)' (')()*+,-/ /1"13(33-6719C<O[WD_V÷稾ͩ˦̣ΖĐ×ĝ̢Ц֟ϟ·}ԭ숷iryĨwhrPD|\KiWt`oZp\kYpK:pS:rT>ePoWlT}ekhhRjTt[q\u_yo[wZLx[LsXClQ=m[|bT{fUn[DaF1gK7iR;iS<t\KpYacn}|smdklurqhnuwv||xwoihi_~OX[\]]\[_\`^_fh™k™kěmȟr̡wʢwʢsʣtˡsɢrɤtǢrȧuƩuƦtɨvʭ{ˬz˫{˯}Ǭ{ʹγ̱Ͳ̱̱Ȯ͵϶ͳ̰ʯ˲ɰɳɶƱȸtm}r`fcIWS8GE)>9 2,,&&$ #$ ""  !"##"$"#$%')&(() () +,$--(./+3409?<NYVKcZѮЧаםǜʙȑ•ȦӞ͟͜Ȓs҈yprul~`OfTfUgVjXiWlZ|\HoO7b=,~]J~aKgWwVIq\zclXgQkUu]qhtYBpV?rWBrVBx_IkYiY|eTt]IeK7lU@eN5[C'aJ3w`MsW]kw|{podophmpsuyxvournlijfd`\[a`_`a]\^```f™kƝoʡsʢu̧z̦y˦wɣtȣsŠmƣoȣpƧtƥrǥtˬzʫ|ʪz˫|˫|̬̱γϳβ˰˱ɯ˱̲͵ϸѺη˷ʴƱëqoyt]ddGMF,C7#6,-%)"%! " "#!"#$#$'(#&'!()"'*"*+'.1,,/./1/6=<MWXI^Xͭ|ب̷욶ŠΧԢѥѩҮԦΛČt݆ukttwuoly^oX}heiQ}flSoWzaM|c{dJnX~_Mo[nY_OzYFcNeSxfnZ{bLhR8pW>qX@kTwbNjW}fRs]KkTBfR9]C)X9$[>+o^H}iN}]kw{xrilornqw}wvtqqollljgggeadba]]]\\[^acfÚkśnƛnƝpˤvƞqʣsʣuˣuɣrȣpɣrʥuɥuȥvʦx˧zͫͬͬήέΰβαͲ̴̴̴ε͵ʹ̵ʴįǮŭįp}^e]HPC)NC,<11&+#'!#!   !##!""#%' '*"(+$+,&+/,-10244=BBOYZHc]~ݮռޫҵپ͐ɠܥpsuqk~h{a~ezpfNoXy]njptVatYoXrZgToXhSz[I~hkT{eu[EqU>v[EhRzaMkO<pZCq\HjU@Y@%ZA"[>#^C.l[DjZ@sVfmw}~{ptpzzrwsy~xtqompkhiiea`acY`ZTX[X\\W_ef—jĜmʣuʤwˢvʡsɢsȟqȞpɡqȡpɢrʢqƠmɥu˧xͬ}˫|̭̭ͭˬ̭̬ͰͲεεʹʹ̳ͳ̴ʳȰƯòrk}o[fYB\Q6@57,,#&!$! !"$%!$!$$((+"'*#*-'*/*.1/04.<C@O[YOf\øݫܪԒ{ߛ̊Ǔӡsqomp[s_oltb}[J]LvWCkVvb{b{bpXoXfOqly^kSiRoUiQybfQz^K{_Ly^Ky^Ou[JwaKm]CS9 V7^F*aL2]F-fP:gU>tdKrX{_fn~{||~rqfle^ddbca_^]][XTTXY[[\[\bc˜hƚlǜoɞqʟr˟rʡsˡsɟqˣrǜlʡpɡpɣqȣsǥtȩy˫|ͭʩ}̬̭̪ήͮϲϴϵγͳ̰˰ʯȯɳƳð}qszasiQ`U<NB(>61))!( % "!"#% "#%$( %+#&+%).(+0+-0.041=CAQ[\\jiļ޳ܨٞś›Ǽ~ˢ̌͆uxb{doX~`O|[IgWdQbNxVGzZMjVfRzZMfVgTw`~hpT{[|dMz[JhSfSn_yer]}du]L|bPoV>_A*[>(gM8hO;gP;eN9gQ;cO6iW@t`JiUlWiip{~~qkmhf`^]ZXWYYV[WU~TT}T[]W[^d`beŘjřjǝnǜoȜoȟoˢtřjǘiȜmɞnȟnȞnǝoơpƢrŠsʤzɦ{ʨ|ʨ~Ϋήбϲͳ϶͵ε˳ʱʱɲĬ}~}shsmXVI8F4%;. 2',"( $! " ""$$'"%*$%*$',&*/,,10266=EFM[^Vhkݶۺ䊭ĠޟȭwfqtgQ~_JgQjRmTbLbNmVdy]jT}]Kx\HgTbPs]wZGaQdSvVF`Stei}c}gTdSu\EhR8jM;y^PxbOdM8eK7cO4^K,_K0^K1hV=s_KydnWfp{}tgd`_^c\ZQ~L}K{H~KNW_`_]\\[\_[^`c–eři×gȜnȜoȟpŘkɛnɞnțmɞoɠpȣrǥsʨxέͮͭέ̬ˬͮϯͰϵηͷ͹ȳƴȴƯ|tj_WIZO?PD1=1"2'.%("#!  "!##&!$)#','(-*(,+-31065>EGN[\Ylk߳≯}ڞͫ|{bo\zbrYkVjWku[mXdOmUqWfpYiTdO~bL}`M|aMtZ|^kU}dy_Kzev`lU~eSnZ{cMrXCw^NycRt`JcL3`D._C+YC$\E)Z@'bK2hW>vdO|cTl\hqx|q|\~^oSnRZ]\\XPOSXaab`_[YX\]ca`^acĚi×hŘjǛkǜlǝoʞpʠoɢqɢrɢq̩xʩz̬|̬{̮~̯ϴγαͰϰϲ̷̲ϹʮǮĮ¬©qj~zaus`b]JLF06/.&)#&!# " ""% #'"$)$%*&).+(-+*0.1:86DAQ\\`lpķܽ۩ףЮږҏʐЍʏϑזݝߢޭ~wdrQGlWpZcTn^s_cQi[cV|_Ox[KjZwdq[hSaOjQuTy`qS{_|]NeTs]nYcTgVgTw^Kw]Nw^OjO=cL6Y@(V:$ZC)\E,N3_G/cO4bO4iR?oYEnUycmsqxvy~|zuj|]oVrW[Z^[XXZ]a``[YZYWVYYZ]d^a•dęgśjƚjǙhŚiǜoɝo̢rɠpˣsʢrȡsͩ{ͬ~ΫϭϯϲϳͰвαϲȩȩɬβȰȴʷűƳųðomjzp`[SDI@,</!.$+$)"&" #!!&""(#%*%%*%'-)'.+*30-76>LKL[\bsyõԦӫؚϳسЛ͛֞܍ΑюçĬ}}iqTD}bPfUeVn^q^vdm^|]OkXvbgTvecToZiXfUpZ}`PeVeR}bQ|ZOcTbPnO<iXfW}aQw\JrTEnSBiS>eU=^F-X@&^L/]I/V?&]G/ZC+XB'[B)[C+cU9vdMsWcjnpotxz{fdMz^JwW^b```_ba__^ZT|QzQ~Q~RY[\]W\^“cėgƚjǛlƛlʡr̤ẉwʤtʦw̨z̨zͨ{ͩ|ȥvϮЭбβα̭ϱϲβ̯ϴͶ˺оʹ˷ȷǼ´|vlwmZeQ@O9*@1"4)-$+$& #"!"!%""'##($',(&,*'0-*401::9EFP_a\rsÜÕݨրݤɣ¤ʷٞȖʳ޸ȋɺɩ|zdtWFnQ=w\Ez[I}ZM`NkYkZdSx_J|ualYsa|g|gdScVk\dUp_rWGlgRqT@gSpZr\mUxcKhM9nWAqdGdT8`I/XC%gY<dV=XB-YB)WA%XB+[B+VA']F+bK5tXK|eTrayh}nonuxy|udhP|aIpU^cc`_`^aV]]b\}TwOtJ{OUX\[ZbZZ^_×eǜlǝoʣṛuʣxͨ{ά~˪{έͮϱ͵ϸϸϷ͵ͱαΰαγδʴ˶Ͷɲʱʴűñv{r[pfSbXBZJ4J=(<2 3+.()"$"  !#"'##($&*'(,+)1.+415;?EOR\jooҧјِۭДͿվ˭ڡϏ̺˾̲w{cv\HiK3pS8}^K_O{_KgRz\IgVfSu^eSbQePt^ydmTcu]kWtYw_IuVDxWD\Is^iuZhQeOoW?r^GscK_I.[@)V9#\E-`I2T9&W='T<"W@(S;%T<%V@(ZA,`D5eL;oZMwdWk]vdzfhgkpoduZ}bQsZDjLzZdea_\XY~R\YZVW|RyNtLxN|P~TX[YW[ĖcǙiȜlȞoƜlʟoǞpˡuϧzЪ}ͩ{ήадзѶϴзϴͲͰͰͲдгβͳ˯˱ɰɰDzį{xe~xbidQXQ7G<$;26,1',$'!%# " $ "'##($(,)(.,(1/)223;>GST^nuњ•كէԢΠĚָؼԟٕɑֿǤМ͑л|}jmZr^xZH_Q{ZL}`Nz^KrR@{YI{YK_Q_RoN=|\K~_M|XMcSw^m]hVbmV^;+z_I{]I}]InVnYkXsXt[CsYFsZGiR>cM6T8!W=$YA)[B,\C.U=%S=$J2M5R>$S>%YB+^I2[G1gR?mXGo[Hq`MscR}mYn]}jY|lWucMq\Gu]Iu[~]bh_zUxRwQzRwOwPwMP|MwN{P{NxN{RT{P|RX\_^_ƕeɝpʡrȡpǟọu̦wΩyΪzЮ}έ}аϰͮ}ˮ~ͬϮά˨вϳѵҷϴβΰͰ̲ɳȲŰŰz}cjbOXL5G8@38+4)/%*"(!$ " "#"'##($',)',+(/,*204<?MY[crzϗƷȞͥҠϞ̫Νշ۶ΡەȔ̼Ұܱ㸨Ŧ̤Ϟ \ No newline at end of file
diff --git a/linmath.h b/linmath.h
new file mode 100644
index 0000000..b4d386c
--- /dev/null
+++ b/linmath.h
@@ -0,0 +1,501 @@
+/*
+ * Copyright (c) 2015-2016 The Khronos Group Inc.
+ * Copyright (c) 2015-2016 Valve Corporation
+ * Copyright (c) 2015-2016 LunarG, Inc.
+ *
+ * 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.
+ *
+ * Relicensed from the WTFPL (http://www.wtfpl.net/faq/).
+ */
+
+#ifndef LINMATH_H
+#define LINMATH_H
+
+#include <math.h>
+
+// Converts degrees to radians.
+#define degreesToRadians(angleDegrees) (angleDegrees * M_PI / 180.0)
+
+// Converts radians to degrees.
+#define radiansToDegrees(angleRadians) (angleRadians * 180.0 / M_PI)
+
+typedef float vec3[3];
+static inline void vec3_add(vec3 r, vec3 const a, vec3 const b) {
+ int i;
+ for (i = 0; i < 3; ++i) r[i] = a[i] + b[i];
+}
+static inline void vec3_sub(vec3 r, vec3 const a, vec3 const b) {
+ int i;
+ for (i = 0; i < 3; ++i) r[i] = a[i] - b[i];
+}
+static inline void vec3_scale(vec3 r, vec3 const v, float const s) {
+ int i;
+ for (i = 0; i < 3; ++i) r[i] = v[i] * s;
+}
+static inline float vec3_mul_inner(vec3 const a, vec3 const b) {
+ float p = 0.f;
+ int i;
+ for (i = 0; i < 3; ++i) p += b[i] * a[i];
+ return p;
+}
+static inline void vec3_mul_cross(vec3 r, vec3 const a, vec3 const b) {
+ r[0] = a[1] * b[2] - a[2] * b[1];
+ r[1] = a[2] * b[0] - a[0] * b[2];
+ r[2] = a[0] * b[1] - a[1] * b[0];
+}
+static inline float vec3_len(vec3 const v) { return sqrtf(vec3_mul_inner(v, v)); }
+static inline void vec3_norm(vec3 r, vec3 const v) {
+ float k = 1.f / vec3_len(v);
+ vec3_scale(r, v, k);
+}
+static inline void vec3_reflect(vec3 r, vec3 const v, vec3 const n) {
+ float p = 2.f * vec3_mul_inner(v, n);
+ int i;
+ for (i = 0; i < 3; ++i) r[i] = v[i] - p * n[i];
+}
+
+typedef float vec4[4];
+static inline void vec4_add(vec4 r, vec4 const a, vec4 const b) {
+ int i;
+ for (i = 0; i < 4; ++i) r[i] = a[i] + b[i];
+}
+static inline void vec4_sub(vec4 r, vec4 const a, vec4 const b) {
+ int i;
+ for (i = 0; i < 4; ++i) r[i] = a[i] - b[i];
+}
+static inline void vec4_scale(vec4 r, vec4 v, float s) {
+ int i;
+ for (i = 0; i < 4; ++i) r[i] = v[i] * s;
+}
+static inline float vec4_mul_inner(vec4 a, vec4 b) {
+ float p = 0.f;
+ int i;
+ for (i = 0; i < 4; ++i) p += b[i] * a[i];
+ return p;
+}
+static inline void vec4_mul_cross(vec4 r, vec4 a, vec4 b) {
+ r[0] = a[1] * b[2] - a[2] * b[1];
+ r[1] = a[2] * b[0] - a[0] * b[2];
+ r[2] = a[0] * b[1] - a[1] * b[0];
+ r[3] = 1.f;
+}
+static inline float vec4_len(vec4 v) { return sqrtf(vec4_mul_inner(v, v)); }
+static inline void vec4_norm(vec4 r, vec4 v) {
+ float k = 1.f / vec4_len(v);
+ vec4_scale(r, v, k);
+}
+static inline void vec4_reflect(vec4 r, vec4 v, vec4 n) {
+ float p = 2.f * vec4_mul_inner(v, n);
+ int i;
+ for (i = 0; i < 4; ++i) r[i] = v[i] - p * n[i];
+}
+
+typedef vec4 mat4x4[4];
+static inline void mat4x4_identity(mat4x4 M) {
+ int i, j;
+ for (i = 0; i < 4; ++i)
+ for (j = 0; j < 4; ++j) M[i][j] = i == j ? 1.f : 0.f;
+}
+static inline void mat4x4_dup(mat4x4 M, mat4x4 N) {
+ int i, j;
+ for (i = 0; i < 4; ++i)
+ for (j = 0; j < 4; ++j) M[i][j] = N[i][j];
+}
+static inline void mat4x4_row(vec4 r, mat4x4 M, int i) {
+ int k;
+ for (k = 0; k < 4; ++k) r[k] = M[k][i];
+}
+static inline void mat4x4_col(vec4 r, mat4x4 M, int i) {
+ int k;
+ for (k = 0; k < 4; ++k) r[k] = M[i][k];
+}
+static inline void mat4x4_transpose(mat4x4 M, mat4x4 N) {
+ int i, j;
+ for (j = 0; j < 4; ++j)
+ for (i = 0; i < 4; ++i) M[i][j] = N[j][i];
+}
+static inline void mat4x4_add(mat4x4 M, mat4x4 a, mat4x4 b) {
+ int i;
+ for (i = 0; i < 4; ++i) vec4_add(M[i], a[i], b[i]);
+}
+static inline void mat4x4_sub(mat4x4 M, mat4x4 a, mat4x4 b) {
+ int i;
+ for (i = 0; i < 4; ++i) vec4_sub(M[i], a[i], b[i]);
+}
+static inline void mat4x4_scale(mat4x4 M, mat4x4 a, float k) {
+ int i;
+ for (i = 0; i < 4; ++i) vec4_scale(M[i], a[i], k);
+}
+static inline void mat4x4_scale_aniso(mat4x4 M, mat4x4 a, float x, float y, float z) {
+ int i;
+ vec4_scale(M[0], a[0], x);
+ vec4_scale(M[1], a[1], y);
+ vec4_scale(M[2], a[2], z);
+ for (i = 0; i < 4; ++i) {
+ M[3][i] = a[3][i];
+ }
+}
+static inline void mat4x4_mul(mat4x4 M, mat4x4 a, mat4x4 b) {
+ int k, r, c;
+ for (c = 0; c < 4; ++c)
+ for (r = 0; r < 4; ++r) {
+ M[c][r] = 0.f;
+ for (k = 0; k < 4; ++k) M[c][r] += a[k][r] * b[c][k];
+ }
+}
+static inline void mat4x4_mul_vec4(vec4 r, mat4x4 M, vec4 v) {
+ int i, j;
+ for (j = 0; j < 4; ++j) {
+ r[j] = 0.f;
+ for (i = 0; i < 4; ++i) r[j] += M[i][j] * v[i];
+ }
+}
+static inline void mat4x4_translate(mat4x4 T, float x, float y, float z) {
+ mat4x4_identity(T);
+ T[3][0] = x;
+ T[3][1] = y;
+ T[3][2] = z;
+}
+static inline void mat4x4_translate_in_place(mat4x4 M, float x, float y, float z) {
+ vec4 t = {x, y, z, 0};
+ vec4 r;
+ int i;
+ for (i = 0; i < 4; ++i) {
+ mat4x4_row(r, M, i);
+ M[3][i] += vec4_mul_inner(r, t);
+ }
+}
+static inline void mat4x4_from_vec3_mul_outer(mat4x4 M, vec3 a, vec3 b) {
+ int i, j;
+ for (i = 0; i < 4; ++i)
+ for (j = 0; j < 4; ++j) M[i][j] = i < 3 && j < 3 ? a[i] * b[j] : 0.f;
+}
+static inline void mat4x4_rotate(mat4x4 R, mat4x4 M, float x, float y, float z, float angle) {
+ float s = sinf(angle);
+ float c = cosf(angle);
+ vec3 u = {x, y, z};
+
+ if (vec3_len(u) > 1e-4) {
+ vec3_norm(u, u);
+ mat4x4 T;
+ mat4x4_from_vec3_mul_outer(T, u, u);
+
+ mat4x4 S = {{0, u[2], -u[1], 0}, {-u[2], 0, u[0], 0}, {u[1], -u[0], 0, 0}, {0, 0, 0, 0}};
+ mat4x4_scale(S, S, s);
+
+ mat4x4 C;
+ mat4x4_identity(C);
+ mat4x4_sub(C, C, T);
+
+ mat4x4_scale(C, C, c);
+
+ mat4x4_add(T, T, C);
+ mat4x4_add(T, T, S);
+
+ T[3][3] = 1.;
+ mat4x4_mul(R, M, T);
+ } else {
+ mat4x4_dup(R, M);
+ }
+}
+static inline void mat4x4_rotate_X(mat4x4 Q, mat4x4 M, float angle) {
+ float s = sinf(angle);
+ float c = cosf(angle);
+ mat4x4 R = {{1.f, 0.f, 0.f, 0.f}, {0.f, c, s, 0.f}, {0.f, -s, c, 0.f}, {0.f, 0.f, 0.f, 1.f}};
+ mat4x4_mul(Q, M, R);
+}
+static inline void mat4x4_rotate_Y(mat4x4 Q, mat4x4 M, float angle) {
+ float s = sinf(angle);
+ float c = cosf(angle);
+ mat4x4 R = {{c, 0.f, s, 0.f}, {0.f, 1.f, 0.f, 0.f}, {-s, 0.f, c, 0.f}, {0.f, 0.f, 0.f, 1.f}};
+ mat4x4_mul(Q, M, R);
+}
+static inline void mat4x4_rotate_Z(mat4x4 Q, mat4x4 M, float angle) {
+ float s = sinf(angle);
+ float c = cosf(angle);
+ mat4x4 R = {{c, s, 0.f, 0.f}, {-s, c, 0.f, 0.f}, {0.f, 0.f, 1.f, 0.f}, {0.f, 0.f, 0.f, 1.f}};
+ mat4x4_mul(Q, M, R);
+}
+static inline void mat4x4_invert(mat4x4 T, mat4x4 M) {
+ float s[6];
+ float c[6];
+ s[0] = M[0][0] * M[1][1] - M[1][0] * M[0][1];
+ s[1] = M[0][0] * M[1][2] - M[1][0] * M[0][2];
+ s[2] = M[0][0] * M[1][3] - M[1][0] * M[0][3];
+ s[3] = M[0][1] * M[1][2] - M[1][1] * M[0][2];
+ s[4] = M[0][1] * M[1][3] - M[1][1] * M[0][3];
+ s[5] = M[0][2] * M[1][3] - M[1][2] * M[0][3];
+
+ c[0] = M[2][0] * M[3][1] - M[3][0] * M[2][1];
+ c[1] = M[2][0] * M[3][2] - M[3][0] * M[2][2];
+ c[2] = M[2][0] * M[3][3] - M[3][0] * M[2][3];
+ c[3] = M[2][1] * M[3][2] - M[3][1] * M[2][2];
+ c[4] = M[2][1] * M[3][3] - M[3][1] * M[2][3];
+ c[5] = M[2][2] * M[3][3] - M[3][2] * M[2][3];
+
+ /* Assumes it is invertible */
+ float idet = 1.0f / (s[0] * c[5] - s[1] * c[4] + s[2] * c[3] + s[3] * c[2] - s[4] * c[1] + s[5] * c[0]);
+
+ T[0][0] = (M[1][1] * c[5] - M[1][2] * c[4] + M[1][3] * c[3]) * idet;
+ T[0][1] = (-M[0][1] * c[5] + M[0][2] * c[4] - M[0][3] * c[3]) * idet;
+ T[0][2] = (M[3][1] * s[5] - M[3][2] * s[4] + M[3][3] * s[3]) * idet;
+ T[0][3] = (-M[2][1] * s[5] + M[2][2] * s[4] - M[2][3] * s[3]) * idet;
+
+ T[1][0] = (-M[1][0] * c[5] + M[1][2] * c[2] - M[1][3] * c[1]) * idet;
+ T[1][1] = (M[0][0] * c[5] - M[0][2] * c[2] + M[0][3] * c[1]) * idet;
+ T[1][2] = (-M[3][0] * s[5] + M[3][2] * s[2] - M[3][3] * s[1]) * idet;
+ T[1][3] = (M[2][0] * s[5] - M[2][2] * s[2] + M[2][3] * s[1]) * idet;
+
+ T[2][0] = (M[1][0] * c[4] - M[1][1] * c[2] + M[1][3] * c[0]) * idet;
+ T[2][1] = (-M[0][0] * c[4] + M[0][1] * c[2] - M[0][3] * c[0]) * idet;
+ T[2][2] = (M[3][0] * s[4] - M[3][1] * s[2] + M[3][3] * s[0]) * idet;
+ T[2][3] = (-M[2][0] * s[4] + M[2][1] * s[2] - M[2][3] * s[0]) * idet;
+
+ T[3][0] = (-M[1][0] * c[3] + M[1][1] * c[1] - M[1][2] * c[0]) * idet;
+ T[3][1] = (M[0][0] * c[3] - M[0][1] * c[1] + M[0][2] * c[0]) * idet;
+ T[3][2] = (-M[3][0] * s[3] + M[3][1] * s[1] - M[3][2] * s[0]) * idet;
+ T[3][3] = (M[2][0] * s[3] - M[2][1] * s[1] + M[2][2] * s[0]) * idet;
+}
+static inline void mat4x4_orthonormalize(mat4x4 R, mat4x4 M) {
+ mat4x4_dup(R, M);
+ float s = 1.;
+ vec3 h;
+
+ vec3_norm(R[2], R[2]);
+
+ s = vec3_mul_inner(R[1], R[2]);
+ vec3_scale(h, R[2], s);
+ vec3_sub(R[1], R[1], h);
+ vec3_norm(R[2], R[2]);
+
+ s = vec3_mul_inner(R[1], R[2]);
+ vec3_scale(h, R[2], s);
+ vec3_sub(R[1], R[1], h);
+ vec3_norm(R[1], R[1]);
+
+ s = vec3_mul_inner(R[0], R[1]);
+ vec3_scale(h, R[1], s);
+ vec3_sub(R[0], R[0], h);
+ vec3_norm(R[0], R[0]);
+}
+
+static inline void mat4x4_frustum(mat4x4 M, float l, float r, float b, float t, float n, float f) {
+ M[0][0] = 2.f * n / (r - l);
+ M[0][1] = M[0][2] = M[0][3] = 0.f;
+
+ M[1][1] = 2.f * n / (t - b);
+ M[1][0] = M[1][2] = M[1][3] = 0.f;
+
+ M[2][0] = (r + l) / (r - l);
+ M[2][1] = (t + b) / (t - b);
+ M[2][2] = -(f + n) / (f - n);
+ M[2][3] = -1.f;
+
+ M[3][2] = -2.f * (f * n) / (f - n);
+ M[3][0] = M[3][1] = M[3][3] = 0.f;
+}
+static inline void mat4x4_ortho(mat4x4 M, float l, float r, float b, float t, float n, float f) {
+ M[0][0] = 2.f / (r - l);
+ M[0][1] = M[0][2] = M[0][3] = 0.f;
+
+ M[1][1] = 2.f / (t - b);
+ M[1][0] = M[1][2] = M[1][3] = 0.f;
+
+ M[2][2] = -2.f / (f - n);
+ M[2][0] = M[2][1] = M[2][3] = 0.f;
+
+ M[3][0] = -(r + l) / (r - l);
+ M[3][1] = -(t + b) / (t - b);
+ M[3][2] = -(f + n) / (f - n);
+ M[3][3] = 1.f;
+}
+static inline void mat4x4_perspective(mat4x4 m, float y_fov, float aspect, float n, float f) {
+ /* NOTE: Degrees are an unhandy unit to work with.
+ * linmath.h uses radians for everything! */
+ float const a = (float)(1.f / tan(y_fov / 2.f));
+
+ m[0][0] = a / aspect;
+ m[0][1] = 0.f;
+ m[0][2] = 0.f;
+ m[0][3] = 0.f;
+
+ m[1][0] = 0.f;
+ m[1][1] = a;
+ m[1][2] = 0.f;
+ m[1][3] = 0.f;
+
+ m[2][0] = 0.f;
+ m[2][1] = 0.f;
+ m[2][2] = -((f + n) / (f - n));
+ m[2][3] = -1.f;
+
+ m[3][0] = 0.f;
+ m[3][1] = 0.f;
+ m[3][2] = -((2.f * f * n) / (f - n));
+ m[3][3] = 0.f;
+}
+static inline void mat4x4_look_at(mat4x4 m, vec3 eye, vec3 center, vec3 up) {
+ /* Adapted from Android's OpenGL Matrix.java. */
+ /* See the OpenGL GLUT documentation for gluLookAt for a description */
+ /* of the algorithm. We implement it in a straightforward way: */
+
+ /* TODO: The negation of of can be spared by swapping the order of
+ * operands in the following cross products in the right way. */
+ vec3 f;
+ vec3_sub(f, center, eye);
+ vec3_norm(f, f);
+
+ vec3 s;
+ vec3_mul_cross(s, f, up);
+ vec3_norm(s, s);
+
+ vec3 t;
+ vec3_mul_cross(t, s, f);
+
+ m[0][0] = s[0];
+ m[0][1] = t[0];
+ m[0][2] = -f[0];
+ m[0][3] = 0.f;
+
+ m[1][0] = s[1];
+ m[1][1] = t[1];
+ m[1][2] = -f[1];
+ m[1][3] = 0.f;
+
+ m[2][0] = s[2];
+ m[2][1] = t[2];
+ m[2][2] = -f[2];
+ m[2][3] = 0.f;
+
+ m[3][0] = 0.f;
+ m[3][1] = 0.f;
+ m[3][2] = 0.f;
+ m[3][3] = 1.f;
+
+ mat4x4_translate_in_place(m, -eye[0], -eye[1], -eye[2]);
+}
+
+typedef float quat[4];
+static inline void quat_identity(quat q) {
+ q[0] = q[1] = q[2] = 0.f;
+ q[3] = 1.f;
+}
+static inline void quat_add(quat r, quat a, quat b) {
+ int i;
+ for (i = 0; i < 4; ++i) r[i] = a[i] + b[i];
+}
+static inline void quat_sub(quat r, quat a, quat b) {
+ int i;
+ for (i = 0; i < 4; ++i) r[i] = a[i] - b[i];
+}
+static inline void quat_mul(quat r, quat p, quat q) {
+ vec3 w;
+ vec3_mul_cross(r, p, q);
+ vec3_scale(w, p, q[3]);
+ vec3_add(r, r, w);
+ vec3_scale(w, q, p[3]);
+ vec3_add(r, r, w);
+ r[3] = p[3] * q[3] - vec3_mul_inner(p, q);
+}
+static inline void quat_scale(quat r, quat v, float s) {
+ int i;
+ for (i = 0; i < 4; ++i) r[i] = v[i] * s;
+}
+static inline float quat_inner_product(quat a, quat b) {
+ float p = 0.f;
+ int i;
+ for (i = 0; i < 4; ++i) p += b[i] * a[i];
+ return p;
+}
+static inline void quat_conj(quat r, quat q) {
+ int i;
+ for (i = 0; i < 3; ++i) r[i] = -q[i];
+ r[3] = q[3];
+}
+#define quat_norm vec4_norm
+static inline void quat_mul_vec3(vec3 r, quat q, vec3 v) {
+ quat v_ = {v[0], v[1], v[2], 0.f};
+
+ quat_conj(r, q);
+ quat_norm(r, r);
+ quat_mul(r, v_, r);
+ quat_mul(r, q, r);
+}
+static inline void mat4x4_from_quat(mat4x4 M, quat q) {
+ float a = q[3];
+ float b = q[0];
+ float c = q[1];
+ float d = q[2];
+ float a2 = a * a;
+ float b2 = b * b;
+ float c2 = c * c;
+ float d2 = d * d;
+
+ M[0][0] = a2 + b2 - c2 - d2;
+ M[0][1] = 2.f * (b * c + a * d);
+ M[0][2] = 2.f * (b * d - a * c);
+ M[0][3] = 0.f;
+
+ M[1][0] = 2 * (b * c - a * d);
+ M[1][1] = a2 - b2 + c2 - d2;
+ M[1][2] = 2.f * (c * d + a * b);
+ M[1][3] = 0.f;
+
+ M[2][0] = 2.f * (b * d + a * c);
+ M[2][1] = 2.f * (c * d - a * b);
+ M[2][2] = a2 - b2 - c2 + d2;
+ M[2][3] = 0.f;
+
+ M[3][0] = M[3][1] = M[3][2] = 0.f;
+ M[3][3] = 1.f;
+}
+
+static inline void mat4x4o_mul_quat(mat4x4 R, mat4x4 M, quat q) {
+ /* XXX: The way this is written only works for othogonal matrices. */
+ /* TODO: Take care of non-orthogonal case. */
+ quat_mul_vec3(R[0], q, M[0]);
+ quat_mul_vec3(R[1], q, M[1]);
+ quat_mul_vec3(R[2], q, M[2]);
+
+ R[3][0] = R[3][1] = R[3][2] = 0.f;
+ R[3][3] = 1.f;
+}
+static inline void quat_from_mat4x4(quat q, mat4x4 M) {
+ float r = 0.f;
+ int i;
+
+ int perm[] = {0, 1, 2, 0, 1};
+ int *p = perm;
+
+ for (i = 0; i < 3; i++) {
+ float m = M[i][i];
+ if (m < r) continue;
+ m = r;
+ p = &perm[i];
+ }
+
+ r = sqrtf(1.f + M[p[0]][p[0]] - M[p[1]][p[1]] - M[p[2]][p[2]]);
+
+ if (r < 1e-6) {
+ q[0] = 1.f;
+ q[1] = q[2] = q[3] = 0.f;
+ return;
+ }
+
+ q[0] = r / 2.f;
+ q[1] = (M[p[0]][p[1]] - M[p[1]][p[0]]) / (2.f * r);
+ q[2] = (M[p[2]][p[0]] - M[p[0]][p[2]]) / (2.f * r);
+ q[3] = (M[p[2]][p[1]] - M[p[1]][p[2]]) / (2.f * r);
+}
+
+#endif