/* * Copyright (C) 2011 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. */ #include "RenderControl.h" #include #include #include #include #include #include "ChecksumCalculatorThreadInfo.h" #include "FrameBuffer.h" #include "GLESVersionDetector.h" #include "OpenGLESDispatch/DispatchTables.h" #include "OpenGLESDispatch/EGLDispatch.h" #include "RenderThreadInfo.h" #include "RenderThreadInfoGl.h" #include "SyncThread.h" #include "aemu/base/Tracing.h" #include "host-common/logging.h" #include "host-common/misc.h" #include "host-common/opengl/misc.h" #include "host-common/sync_device.h" #include "compressedTextureFormats/AstcCpuDecompressor.h" #include "vulkan/VkCommonOperations.h" #include "vulkan/VkDecoderGlobalState.h" namespace gfxstream { using android::base::AutoLock; using android::base::Lock; using emugl::emugl_sync_device_exists; using emugl::emugl_sync_register_trigger_wait; using gl::EmulatedEglFenceSync; using gl::GLES_DISPATCH_MAX_VERSION_2; using gl::GLES_DISPATCH_MAX_VERSION_3_0; using gl::GLES_DISPATCH_MAX_VERSION_3_1; using gl::GLESApi; using gl::GLESApi_CM; using gl::GLESDispatchMaxVersion; using gl::RenderThreadInfoGl; #define DEBUG 0 #define DEBUG_GRALLOC_SYNC 0 #define DEBUG_EGL_SYNC 0 #define RENDERCONTROL_DPRINT(...) \ do { \ if (DEBUG) { \ fprintf(stderr, __VA_ARGS__); \ } \ } while (0) #if DEBUG_GRALLOC_SYNC #define GRSYNC_DPRINT RENDERCONTROL_DPRINT #else #define GRSYNC_DPRINT(...) #endif #if DEBUG_EGL_SYNC #define EGLSYNC_DPRINT RENDERCONTROL_DPRINT #else #define EGLSYNC_DPRINT(...) #endif // GrallocSync is a class that helps to reflect the behavior of // gralloc_lock/gralloc_unlock on the guest. // If we don't use this, apps that use gralloc buffers (such as webcam) // will have out-of-order frames, // as GL calls from different threads in the guest // are allowed to arrive at the host in any ordering. class GrallocSync { public: GrallocSync() { // Having in-order webcam frames is nice, but not at the cost // of potential deadlocks; // we need to be careful of what situations in which // we actually lock/unlock the gralloc color buffer. // // To avoid deadlock: // we require rcColorBufferCacheFlush to be called // whenever gralloc_lock is called on the guest, // and we require rcUpdateWindowColorBuffer to be called // whenever gralloc_unlock is called on the guest. // // Some versions of the system image optimize out // the call to rcUpdateWindowColorBuffer in the case of zero // width/height, but since we're using that as synchronization, // that lack of calling can lead to a deadlock on the host // in many situations // (switching camera sides, exiting benchmark apps, etc). // So, we put GrallocSync under the feature control. mEnabled = FrameBuffer::getFB()->getFeatures().GrallocSync.enabled; // There are two potential tricky situations to handle: // a. Multiple users of gralloc buffers that all want to // call gralloc_lock. This is obeserved to happen on older APIs // (<= 19). // b. The pipe doesn't have to preserve ordering of the // gralloc_lock and gralloc_unlock commands themselves. // // To handle a), notice the situation is one of one type of user // needing multiple locks that needs to exclude concurrent use // by another type of user. This maps well to a read/write lock, // where gralloc_lock and gralloc_unlock users are readers // and rcFlushWindowColorBuffer is the writer. // From the perspective of the host preparing and posting // buffers, these are indeed read/write operations. // // To handle b), we give up on locking when the state is observed // to be bad. lockState tracks how many color buffer locks there are. // If lockState < 0, it means we definitely have an unlock before lock // sort of situation, and should give up. lockState = 0; } // lockColorBufferPrepare is designed to handle // gralloc_lock/unlock requests, and uses the read lock. // When rcFlushWindowColorBuffer is called (when frames are posted), // we use the write lock (see GrallocSyncPostLock). void lockColorBufferPrepare() { int newLockState = ++lockState; if (mEnabled && newLockState == 1) { mGrallocColorBufferLock.lockRead(); } else if (mEnabled) { GRSYNC_DPRINT("warning: recursive/multiple locks from guest!"); } } void unlockColorBufferPrepare() { int newLockState = --lockState; if (mEnabled && newLockState == 0) mGrallocColorBufferLock.unlockRead(); } android::base::ReadWriteLock mGrallocColorBufferLock; private: bool mEnabled; std::atomic lockState; DISALLOW_COPY_ASSIGN_AND_MOVE(GrallocSync); }; class GrallocSyncPostLock : public android::base::AutoWriteLock { public: GrallocSyncPostLock(GrallocSync& grallocsync) : android::base::AutoWriteLock(grallocsync.mGrallocColorBufferLock) { } }; static GrallocSync* sGrallocSync() { static GrallocSync* g = new GrallocSync; return g; } static const GLint rendererVersion = 1; // GLAsyncSwap version history: // "ANDROID_EMU_NATIVE_SYNC": original version // "ANDROIDEMU_native_sync_v2": +cleanup of sync objects // "ANDROIDEMU_native_sync_v3": EGL_KHR_wait_sync // "ANDROIDEMU_native_sync_v4": Correct eglGetSyncAttrib via rcIsSyncSignaled // (We need all the different strings to not be prefixes of any other // due to how they are checked for in the GL extensions on the guest) static const char* kAsyncSwapStrV2 = "ANDROID_EMU_native_sync_v2"; static const char* kAsyncSwapStrV3 = "ANDROID_EMU_native_sync_v3"; static const char* kAsyncSwapStrV4 = "ANDROID_EMU_native_sync_v4"; // DMA version history: // "ANDROID_EMU_dma_v1": add dma device and rcUpdateColorBufferDMA and do // yv12 conversion on the GPU // "ANDROID_EMU_dma_v2": adds DMA support glMapBufferRange (and unmap) static const char* kDma1Str = "ANDROID_EMU_dma_v1"; static const char* kDma2Str = "ANDROID_EMU_dma_v2"; static const char* kDirectMemStr = "ANDROID_EMU_direct_mem"; // GLESDynamicVersion: up to 3.1 so far static const char* kGLESDynamicVersion_2 = "ANDROID_EMU_gles_max_version_2"; static const char* kGLESDynamicVersion_3_0 = "ANDROID_EMU_gles_max_version_3_0"; static const char* kGLESDynamicVersion_3_1 = "ANDROID_EMU_gles_max_version_3_1"; // HWComposer Host Composition static const char* kHostCompositionV1 = "ANDROID_EMU_host_composition_v1"; static const char* kHostCompositionV2 = "ANDROID_EMU_host_composition_v2"; // Vulkan static const char* kVulkanFeatureStr = "ANDROID_EMU_vulkan"; static const char* kDeferredVulkanCommands = "ANDROID_EMU_deferred_vulkan_commands"; static const char* kVulkanNullOptionalStrings = "ANDROID_EMU_vulkan_null_optional_strings"; static const char* kVulkanCreateResourcesWithRequirements = "ANDROID_EMU_vulkan_create_resources_with_requirements"; // treat YUV420_888 as NV21 static const char* kYUV420888toNV21 = "ANDROID_EMU_YUV420_888_to_NV21"; // Cache YUV frame static const char* kYUVCache = "ANDROID_EMU_YUV_Cache"; // GL protocol v2 static const char* kAsyncUnmapBuffer = "ANDROID_EMU_async_unmap_buffer"; // Vulkan: Correct marshaling for ignored handles static const char* kVulkanIgnoredHandles = "ANDROID_EMU_vulkan_ignored_handles"; // virtio-gpu-next static const char* kVirtioGpuNext = "ANDROID_EMU_virtio_gpu_next"; // address space subdevices static const char* kHasSharedSlotsHostMemoryAllocator = "ANDROID_EMU_has_shared_slots_host_memory_allocator"; // vulkan free memory sync static const char* kVulkanFreeMemorySync = "ANDROID_EMU_vulkan_free_memory_sync"; // virtio-gpu native sync static const char* kVirtioGpuNativeSync = "ANDROID_EMU_virtio_gpu_native_sync"; // Struct defs for VK_KHR_shader_float16_int8 static const char* kVulkanShaderFloat16Int8 = "ANDROID_EMU_vulkan_shader_float16_int8"; // Async queue submit static const char* kVulkanAsyncQueueSubmit = "ANDROID_EMU_vulkan_async_queue_submit"; // Host side tracing static const char* kHostSideTracing = "ANDROID_EMU_host_side_tracing"; // Some frame commands we can easily make async // rcMakeCurrent // rcCompose // rcDestroySyncKHR static const char* kAsyncFrameCommands = "ANDROID_EMU_async_frame_commands"; // Queue submit with commands static const char* kVulkanQueueSubmitWithCommands = "ANDROID_EMU_vulkan_queue_submit_with_commands"; // Batched descriptor set update static const char* kVulkanBatchedDescriptorSetUpdate = "ANDROID_EMU_vulkan_batched_descriptor_set_update"; // Synchronized glBufferData call static const char* kSyncBufferData = "ANDROID_EMU_sync_buffer_data"; // Async vkQSRI static const char* kVulkanAsyncQsri = "ANDROID_EMU_vulkan_async_qsri"; // Read color buffer DMA static const char* kReadColorBufferDma = "ANDROID_EMU_read_color_buffer_dma"; // Multiple display configs static const char* kHWCMultiConfigs= "ANDROID_EMU_hwc_multi_configs"; static constexpr const uint64_t kInvalidPUID = std::numeric_limits::max(); static void rcTriggerWait(uint64_t glsync_ptr, uint64_t thread_ptr, uint64_t timeline); void registerTriggerWait() { emugl_sync_register_trigger_wait(rcTriggerWait); } static GLint rcGetRendererVersion() { registerTriggerWait(); sGrallocSync(); return rendererVersion; } static EGLint rcGetEGLVersion(EGLint* major, EGLint* minor) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return EGL_FALSE; } fb->getEmulationGl().getEglVersion(major, minor); return EGL_TRUE; } static EGLint rcQueryEGLString(EGLenum name, void* buffer, EGLint bufferSize) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return 0; } const char* str = gl::s_egl.eglQueryString(fb->getDisplay(), name); if (!str) { return 0; } std::string eglStr(str); if ((fb->getMaxGLESVersion() >= GLES_DISPATCH_MAX_VERSION_3_0) && fb->getFeatures().GlesDynamicVersion.enabled && eglStr.find("EGL_KHR_create_context") == std::string::npos) { eglStr += "EGL_KHR_create_context "; } int len = eglStr.size() + 1; if (!buffer || len > bufferSize) { return -len; } strcpy((char *)buffer, eglStr.c_str()); return len; } static bool shouldEnableAsyncSwap(const gfxstream::host::FeatureSet& features) { bool isPhone = true; bool playStoreImage = features.PlayStoreImage.enabled; return features.GlAsyncSwap.enabled && emugl_sync_device_exists() && (isPhone || playStoreImage) && sizeof(void*) == 8; } static bool shouldEnableVulkan(const gfxstream::host::FeatureSet& features) { // TODO: Restrict further to devices supporting external memory. return features.Vulkan.enabled && vk::getGlobalVkEmulation() && vk::VkDecoderGlobalState::get()->getHostFeatureSupport().supportsVulkan; } static bool shouldEnableDeferredVulkanCommands() { auto supportInfo = vk::VkDecoderGlobalState::get()->getHostFeatureSupport(); return supportInfo.supportsVulkan && supportInfo.useDeferredCommands; } static bool shouldEnableCreateResourcesWithRequirements() { auto supportInfo = vk::VkDecoderGlobalState::get()->getHostFeatureSupport(); return supportInfo.supportsVulkan && supportInfo.useCreateResourcesWithRequirements; } static bool shouldEnableVulkanShaderFloat16Int8(const gfxstream::host::FeatureSet& features) { return shouldEnableVulkan(features) && features.VulkanShaderFloat16Int8.enabled; } static bool shouldEnableAsyncQueueSubmit(const gfxstream::host::FeatureSet& features) { return shouldEnableVulkan(features); } static bool shouldEnableVulkanAsyncQsri(const gfxstream::host::FeatureSet& features) { return shouldEnableVulkan(features) && (features.GlAsyncSwap.enabled || (features.VirtioGpuNativeSync.enabled && features.VirtioGpuFenceContexts.enabled)); } static bool shouldEnableVsyncGatedSyncFences(const gfxstream::host::FeatureSet& features) { return shouldEnableAsyncSwap(features); } const char* maxVersionToFeatureString(GLESDispatchMaxVersion version) { switch (version) { case GLES_DISPATCH_MAX_VERSION_2: return kGLESDynamicVersion_2; case GLES_DISPATCH_MAX_VERSION_3_0: return kGLESDynamicVersion_3_0; case GLES_DISPATCH_MAX_VERSION_3_1: return kGLESDynamicVersion_3_1; default: return kGLESDynamicVersion_2; } } static bool shouldEnableQueueSubmitWithCommands(const gfxstream::host::FeatureSet& features) { return shouldEnableVulkan(features) && features.VulkanQueueSubmitWithCommands.enabled; } static bool shouldEnableBatchedDescriptorSetUpdate(const gfxstream::host::FeatureSet& features) { return shouldEnableVulkan(features) && shouldEnableQueueSubmitWithCommands(features) && features.VulkanBatchedDescriptorSetUpdate.enabled; } // OpenGL ES 3.x support involves changing the GL_VERSION string, which is // assumed to be formatted in the following way: // "OpenGL ES-CM 1.m " or // "OpenGL ES M.m " // where M is the major version number and m is minor version number. If the // GL_VERSION string doesn't reflect the maximum available version of OpenGL // ES, many apps will not be able to detect support. We need to mess with the // version string in the first place since the underlying backend (whether it // is Translator, SwiftShader, ANGLE, et al) may not advertise a GL_VERSION // string reflecting their maximum capabilities. std::string replaceESVersionString(const std::string& prev, const std::string& newver) { // There is no need to fiddle with the string // if we are in a ES 1.x context. // Such contexts are considered as a special case that must // be untouched. if (prev.find("ES-CM") != std::string::npos) { return prev; } size_t esStart = prev.find("ES "); size_t esEnd = prev.find(" ", esStart + 3); if (esStart == std::string::npos || esEnd == std::string::npos) { // Account for out-of-spec version strings. fprintf(stderr, "%s: Error: invalid OpenGL ES version string %s\n", __func__, prev.c_str()); return prev; } std::string res = prev.substr(0, esStart + 3); res += newver; res += prev.substr(esEnd); return res; } // If the GLES3 feature is disabled, we also want to splice out // OpenGL extensions that should not appear in a GLES2 system. void removeExtension(std::string& currExts, const std::string& toRemove) { size_t pos = currExts.find(toRemove); if (pos != std::string::npos) currExts.erase(pos, toRemove.length()); } static EGLint rcGetGLString(EGLenum name, void* buffer, EGLint bufferSize) { RenderThreadInfoGl* const tInfo = RenderThreadInfoGl::get(); // whatever we end up returning, // it will have a terminating \0, // so account for it here. std::string glStr; if (tInfo && tInfo->currContext.get()) { const char *str = nullptr; if (tInfo->currContext->clientVersion() > GLESApi_CM) { str = (const char*)gl::s_gles2.glGetString(name); } else { str = (const char*)gl::s_gles1.glGetString(name); } if (str) { glStr += str; } } FrameBuffer* fb = FrameBuffer::getFB(); const gfxstream::host::FeatureSet& features = fb->getFeatures(); // We add the maximum supported GL protocol number into GL_EXTENSIONS // filter extensions by name to match guest-side support GLESDispatchMaxVersion maxVersion = fb->getMaxGLESVersion(); if (name == GL_EXTENSIONS) { glStr = gl::filterExtensionsBasedOnMaxVersion(features, maxVersion, glStr); } bool isChecksumEnabled = features.GlPipeChecksum.enabled; bool asyncSwapEnabled = shouldEnableAsyncSwap(features); bool virtioGpuNativeSyncEnabled = features.VirtioGpuNativeSync.enabled; bool dma1Enabled = features.GlDma.enabled; bool dma2Enabled = features.GlDma2.enabled; bool directMemEnabled = features.GlDirectMem.enabled; bool hostCompositionEnabled = features.HostComposition.enabled; bool vulkanEnabled = shouldEnableVulkan(features); bool deferredVulkanCommandsEnabled = shouldEnableVulkan(features) && shouldEnableDeferredVulkanCommands(); bool vulkanNullOptionalStringsEnabled = shouldEnableVulkan(features) && features.VulkanNullOptionalStrings.enabled; bool vulkanCreateResourceWithRequirementsEnabled = shouldEnableVulkan(features) && shouldEnableCreateResourcesWithRequirements(); bool YUV420888toNV21Enabled = features.Yuv420888ToNv21.enabled; bool YUVCacheEnabled = features.YuvCache.enabled; bool AsyncUnmapBufferEnabled = features.AsyncComposeSupport.enabled; bool vulkanIgnoredHandlesEnabled = shouldEnableVulkan(features) && features.VulkanIgnoredHandles.enabled; bool virtioGpuNextEnabled = features.VirtioGpuNext.enabled; bool hasSharedSlotsHostMemoryAllocatorEnabled = features.HasSharedSlotsHostMemoryAllocator.enabled; bool vulkanFreeMemorySyncEnabled = shouldEnableVulkan(features); bool vulkanShaderFloat16Int8Enabled = shouldEnableVulkanShaderFloat16Int8(features); bool vulkanAsyncQueueSubmitEnabled = shouldEnableAsyncQueueSubmit(features); bool vulkanQueueSubmitWithCommands = shouldEnableQueueSubmitWithCommands(features); bool vulkanBatchedDescriptorSetUpdate = shouldEnableBatchedDescriptorSetUpdate(features); bool syncBufferDataEnabled = true; bool vulkanAsyncQsri = shouldEnableVulkanAsyncQsri(features); bool readColorBufferDma = directMemEnabled && hasSharedSlotsHostMemoryAllocatorEnabled; bool hwcMultiConfigs = features.HwcMultiConfigs.enabled; if (isChecksumEnabled && name == GL_EXTENSIONS) { glStr += ChecksumCalculatorThreadInfo::getMaxVersionString(); glStr += " "; } if (asyncSwapEnabled && name == GL_EXTENSIONS) { glStr += kAsyncSwapStrV2; glStr += " "; // for compatibility with older system images // Only enable EGL_KHR_wait_sync (and above) for host gpu. if (emugl::getRenderer() == SELECTED_RENDERER_HOST) { glStr += kAsyncSwapStrV3; glStr += " "; glStr += kAsyncSwapStrV4; glStr += " "; } } if (dma1Enabled && name == GL_EXTENSIONS) { glStr += kDma1Str; glStr += " "; } if (dma2Enabled && name == GL_EXTENSIONS) { glStr += kDma2Str; glStr += " "; } if (directMemEnabled && name == GL_EXTENSIONS) { glStr += kDirectMemStr; glStr += " "; } if (hostCompositionEnabled && name == GL_EXTENSIONS) { glStr += kHostCompositionV1; glStr += " "; } if (hostCompositionEnabled && name == GL_EXTENSIONS) { glStr += kHostCompositionV2; glStr += " "; } if (vulkanEnabled && name == GL_EXTENSIONS) { glStr += kVulkanFeatureStr; glStr += " "; } if (deferredVulkanCommandsEnabled && name == GL_EXTENSIONS) { glStr += kDeferredVulkanCommands; glStr += " "; } if (vulkanNullOptionalStringsEnabled && name == GL_EXTENSIONS) { glStr += kVulkanNullOptionalStrings; glStr += " "; } if (vulkanCreateResourceWithRequirementsEnabled && name == GL_EXTENSIONS) { glStr += kVulkanCreateResourcesWithRequirements; glStr += " "; } if (YUV420888toNV21Enabled && name == GL_EXTENSIONS) { glStr += kYUV420888toNV21; glStr += " "; } if (YUVCacheEnabled && name == GL_EXTENSIONS) { glStr += kYUVCache; glStr += " "; } if (AsyncUnmapBufferEnabled && name == GL_EXTENSIONS) { glStr += kAsyncUnmapBuffer; glStr += " "; } if (vulkanIgnoredHandlesEnabled && name == GL_EXTENSIONS) { glStr += kVulkanIgnoredHandles; glStr += " "; } if (virtioGpuNextEnabled && name == GL_EXTENSIONS) { glStr += kVirtioGpuNext; glStr += " "; } if (hasSharedSlotsHostMemoryAllocatorEnabled && name == GL_EXTENSIONS) { glStr += kHasSharedSlotsHostMemoryAllocator; glStr += " "; } if (vulkanFreeMemorySyncEnabled && name == GL_EXTENSIONS) { glStr += kVulkanFreeMemorySync; glStr += " "; } if (vulkanShaderFloat16Int8Enabled && name == GL_EXTENSIONS) { glStr += kVulkanShaderFloat16Int8; glStr += " "; } if (vulkanAsyncQueueSubmitEnabled && name == GL_EXTENSIONS) { glStr += kVulkanAsyncQueueSubmit; glStr += " "; } if (vulkanQueueSubmitWithCommands && name == GL_EXTENSIONS) { glStr += kVulkanQueueSubmitWithCommands; glStr += " "; } if (vulkanBatchedDescriptorSetUpdate && name == GL_EXTENSIONS) { glStr += kVulkanBatchedDescriptorSetUpdate; glStr += " "; } if (virtioGpuNativeSyncEnabled && name == GL_EXTENSIONS) { glStr += kVirtioGpuNativeSync; glStr += " "; } if (syncBufferDataEnabled && name == GL_EXTENSIONS) { glStr += kSyncBufferData; glStr += " "; } if (vulkanAsyncQsri && name == GL_EXTENSIONS) { glStr += kVulkanAsyncQsri; glStr += " "; } if (readColorBufferDma && name == GL_EXTENSIONS) { glStr += kReadColorBufferDma; glStr += " "; } if (hwcMultiConfigs && name == GL_EXTENSIONS) { glStr += kHWCMultiConfigs; glStr += " "; } if (name == GL_EXTENSIONS) { GLESDispatchMaxVersion guestExtVer = GLES_DISPATCH_MAX_VERSION_2; if (features.GlesDynamicVersion.enabled) { // If the image is in ES 3 mode, add GL_OES_EGL_image_external_essl3 for better Skia support. glStr += "GL_OES_EGL_image_external_essl3 "; guestExtVer = maxVersion; } // If we have a GLES3 implementation, add the corresponding // GLESv2 extensions as well. if (maxVersion > GLES_DISPATCH_MAX_VERSION_2) { glStr += "GL_OES_vertex_array_object "; } // ASTC LDR compressed texture support. const std::string& glExtensions = FrameBuffer::getFB()->hasEmulationGl() ? FrameBuffer::getFB()->getEmulationGl().getGlesExtensionsString() : ""; const bool hasNativeAstc = glExtensions.find("GL_KHR_texture_compression_astc_ldr") != std::string::npos; const bool hasAstcDecompressor = vk::AstcCpuDecompressor::get().available(); if (hasNativeAstc || hasAstcDecompressor) { glStr += "GL_KHR_texture_compression_astc_ldr "; } else { RENDERCONTROL_DPRINT( "rcGetGLString: ASTC not supported. CPU decompressor? %d. GL extensions: %s", hasAstcDecompressor, glExtensions.c_str()); } // Host side tracing support. glStr += kHostSideTracing; glStr += " "; if (features.AsyncComposeSupport.enabled) { // Async makecurrent support. glStr += kAsyncFrameCommands; glStr += " "; } glStr += maxVersionToFeatureString(guestExtVer); glStr += " "; } if (name == GL_VERSION) { if (features.GlesDynamicVersion.enabled) { switch (maxVersion) { // Underlying GLES implmentation's max version string // is allowed to be higher than the version of the request // for the context---it can create a higher version context, // and return simply the max possible version overall. case GLES_DISPATCH_MAX_VERSION_2: glStr = replaceESVersionString(glStr, "2.0"); break; case GLES_DISPATCH_MAX_VERSION_3_0: glStr = replaceESVersionString(glStr, "3.0"); break; case GLES_DISPATCH_MAX_VERSION_3_1: glStr = replaceESVersionString(glStr, "3.1"); break; default: break; } } else { glStr = replaceESVersionString(glStr, "2.0"); } } int nextBufferSize = glStr.size() + 1; if (!buffer || nextBufferSize > bufferSize) { return -nextBufferSize; } snprintf((char *)buffer, nextBufferSize, "%s", glStr.c_str()); return nextBufferSize; } static EGLint rcGetNumConfigs(uint32_t* p_numAttribs) { int numConfigs = 0, numAttribs = 0; FrameBuffer::getFB()->getConfigs()->getPackInfo(&numConfigs, &numAttribs); if (p_numAttribs) { *p_numAttribs = static_cast(numAttribs); } return numConfigs; } static EGLint rcGetConfigs(uint32_t bufSize, GLuint* buffer) { GLuint bufferSize = (GLuint)bufSize; return FrameBuffer::getFB()->getConfigs()->packConfigs(bufferSize, buffer); } static EGLint rcChooseConfig(EGLint *attribs, uint32_t attribs_size, uint32_t *configs, uint32_t configs_size) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return 0; } if (attribs_size == 0) { if (configs && configs_size > 0) { // Pick the first config *configs = 0; if (attribs) *attribs = EGL_NONE; } } return fb->getConfigs()->chooseConfig( attribs, (EGLint*)configs, (EGLint)configs_size); } static EGLint rcGetFBParam(EGLint param) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return 0; } return fb->getDisplayConfigsParam(0, param); } static uint32_t rcCreateContext(uint32_t config, uint32_t share, uint32_t glVersion) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return 0; } HandleType ret = fb->createEmulatedEglContext(config, share, (GLESApi)glVersion); return ret; } static void rcDestroyContext(uint32_t context) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return; } fb->destroyEmulatedEglContext(context); } static uint32_t rcCreateWindowSurface(uint32_t config, uint32_t width, uint32_t height) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return 0; } return fb->createEmulatedEglWindowSurface(config, width, height); } static void rcDestroyWindowSurface(uint32_t windowSurface) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return; } fb->destroyEmulatedEglWindowSurface(windowSurface); } static uint32_t rcCreateColorBuffer(uint32_t width, uint32_t height, GLenum internalFormat) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return 0; } return fb->createColorBuffer(width, height, internalFormat, FRAMEWORK_FORMAT_GL_COMPATIBLE); } static uint32_t rcCreateColorBufferDMA(uint32_t width, uint32_t height, GLenum internalFormat, int frameworkFormat) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return 0; } return fb->createColorBuffer(width, height, internalFormat, (FrameworkFormat)frameworkFormat); } static int rcOpenColorBuffer2(uint32_t colorbuffer) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return -1; } return fb->openColorBuffer( colorbuffer ); } static void rcOpenColorBuffer(uint32_t colorbuffer) { (void) rcOpenColorBuffer2(colorbuffer); } static void rcCloseColorBuffer(uint32_t colorbuffer) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return; } fb->closeColorBuffer( colorbuffer ); } static int rcFlushWindowColorBuffer(uint32_t windowSurface) { GRSYNC_DPRINT("waiting for gralloc cb lock"); GrallocSyncPostLock lock(*sGrallocSync()); GRSYNC_DPRINT("lock gralloc cb lock {"); FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { GRSYNC_DPRINT("unlock gralloc cb lock"); return -1; } HandleType colorBufferHandle = fb->getEmulatedEglWindowSurfaceColorBufferHandle(windowSurface); if (!fb->flushEmulatedEglWindowSurfaceColorBuffer(windowSurface)) { GRSYNC_DPRINT("unlock gralloc cb lock }"); return -1; } // Make the GL updates visible to other backings if necessary. fb->flushColorBufferFromGl(colorBufferHandle); GRSYNC_DPRINT("unlock gralloc cb lock }"); return 0; } // Note that even though this calls rcFlushWindowColorBuffer, // the "Async" part is in the return type, which is void // versus return type int for rcFlushWindowColorBuffer. // // The different return type, even while calling the same // functions internally, will end up making the encoder // and decoder use a different protocol. This is because // the encoder generally obeys the following conventions: // // - The encoder will immediately send and wait for a command // result if the return type is not void. // - The encoder will cache the command in a buffer and send // at a convenient time if the return type is void. // // It can also be expensive performance-wise to trigger // sending traffic back to the guest. Generally, the more we avoid // encoding commands that perform two-way traffic, the better. // // Hence, |rcFlushWindowColorBufferAsync| will avoid extra traffic; // with return type void, // the guest will not wait until this function returns, // nor will it immediately send the command, // resulting in more asynchronous behavior. static void rcFlushWindowColorBufferAsync(uint32_t windowSurface) { rcFlushWindowColorBuffer(windowSurface); } static void rcSetWindowColorBuffer(uint32_t windowSurface, uint32_t colorBuffer) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return; } fb->setEmulatedEglWindowSurfaceColorBuffer(windowSurface, colorBuffer); } static EGLint rcMakeCurrent(uint32_t context, uint32_t drawSurf, uint32_t readSurf) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return EGL_FALSE; } bool ret = fb->bindContext(context, drawSurf, readSurf); return (ret ? EGL_TRUE : EGL_FALSE); } static void rcFBPost(uint32_t colorBuffer) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return; } fb->post(colorBuffer); } static void rcFBSetSwapInterval(EGLint interval) { // XXX: TBD - should be implemented } static void rcBindTexture(uint32_t colorBuffer) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return; } // Update for GL use if necessary. fb->invalidateColorBufferForGl(colorBuffer); fb->bindColorBufferToTexture(colorBuffer); } static void rcBindRenderbuffer(uint32_t colorBuffer) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return; } // Update for GL use if necessary. fb->invalidateColorBufferForGl(colorBuffer); fb->bindColorBufferToRenderbuffer(colorBuffer); } static EGLint rcColorBufferCacheFlush(uint32_t colorBuffer, EGLint postCount, int forRead) { // gralloc_lock() on the guest calls rcColorBufferCacheFlush GRSYNC_DPRINT("waiting for gralloc cb lock"); sGrallocSync()->lockColorBufferPrepare(); GRSYNC_DPRINT("lock gralloc cb lock {"); return 0; } static void rcReadColorBuffer(uint32_t colorBuffer, GLint x, GLint y, GLint width, GLint height, GLenum format, GLenum type, void* pixels) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return; } fb->readColorBuffer(colorBuffer, x, y, width, height, format, type, pixels); } static int rcUpdateColorBuffer(uint32_t colorBuffer, GLint x, GLint y, GLint width, GLint height, GLenum format, GLenum type, void* pixels) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { GRSYNC_DPRINT("unlock gralloc cb lock"); sGrallocSync()->unlockColorBufferPrepare(); return -1; } fb->updateColorBuffer(colorBuffer, x, y, width, height, format, type, pixels); GRSYNC_DPRINT("unlock gralloc cb lock"); sGrallocSync()->unlockColorBufferPrepare(); return 0; } static int rcUpdateColorBufferDMA(uint32_t colorBuffer, GLint x, GLint y, GLint width, GLint height, GLenum format, GLenum type, void* pixels, uint32_t pixels_size) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { GRSYNC_DPRINT("unlock gralloc cb lock"); sGrallocSync()->unlockColorBufferPrepare(); return -1; } fb->updateColorBuffer(colorBuffer, x, y, width, height, format, type, pixels); GRSYNC_DPRINT("unlock gralloc cb lock"); sGrallocSync()->unlockColorBufferPrepare(); return 0; } static uint32_t rcCreateClientImage(uint32_t context, EGLenum target, GLuint buffer) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return 0; } return fb->createEmulatedEglImage(context, target, buffer); } static int rcDestroyClientImage(uint32_t image) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return 0; } return fb->destroyEmulatedEglImage(image); } static void rcSelectChecksumHelper(uint32_t protocol, uint32_t reserved) { ChecksumCalculatorThreadInfo::setVersion(protocol); } // |rcTriggerWait| is called from the goldfish sync // kernel driver whenever a native fence fd is created. // We will then need to use the host to find out // when to signal that native fence fd. We use // SyncThread for that. static void rcTriggerWait(uint64_t eglsync_ptr, uint64_t thread_ptr, uint64_t timeline) { if (thread_ptr == 1) { // Is vulkan sync fd; // just signal right away for now EGLSYNC_DPRINT("vkFence=0x%llx timeline=0x%llx", eglsync_ptr, thread_ptr, timeline); SyncThread::get()->triggerWaitVk(reinterpret_cast(eglsync_ptr), timeline); } else if (thread_ptr == 2) { EGLSYNC_DPRINT("vkFence=0x%llx timeline=0x%llx", eglsync_ptr, thread_ptr, timeline); SyncThread::get()->triggerWaitVkQsri(reinterpret_cast(eglsync_ptr), timeline); } else { EmulatedEglFenceSync* fenceSync = EmulatedEglFenceSync::getFromHandle(eglsync_ptr); FrameBuffer *fb = FrameBuffer::getFB(); if (fb && fenceSync && fenceSync->isCompositionFence()) { fb->scheduleVsyncTask([eglsync_ptr, fenceSync, timeline](uint64_t) { EGLSYNC_DPRINT( "vsync: eglsync=0x%llx fenceSync=%p thread_ptr=0x%llx " "timeline=0x%llx", eglsync_ptr, fenceSync, thread_ptr, timeline); SyncThread::get()->triggerWait(fenceSync, timeline); }); } else { EGLSYNC_DPRINT( "eglsync=0x%llx fenceSync=%p thread_ptr=0x%llx " "timeline=0x%llx", eglsync_ptr, fenceSync, thread_ptr, timeline); SyncThread::get()->triggerWait(fenceSync, timeline); } } } // |rcCreateSyncKHR| implements the guest's |eglCreateSyncKHR| by calling the // host's implementation of |eglCreateSyncKHR|. A SyncThread is also notified // for purposes of signaling any native fence fd's that get created in the // guest off the sync object created here. static void rcCreateSyncKHR(EGLenum type, EGLint* attribs, uint32_t num_attribs, int destroyWhenSignaled, uint64_t* outSync, uint64_t* outSyncThread) { // Usually we expect rcTriggerWait to be registered // at the beginning in rcGetRendererVersion, called // on init for all contexts. // But if we are loading from snapshot, that's not // guaranteed, and we need to make sure // rcTriggerWait is registered. emugl_sync_register_trigger_wait(rcTriggerWait); FrameBuffer* fb = FrameBuffer::getFB(); fb->createEmulatedEglFenceSync(type, destroyWhenSignaled, outSync, outSyncThread); RenderThreadInfo* tInfo = RenderThreadInfo::get(); if (tInfo && outSync && shouldEnableVsyncGatedSyncFences(fb->getFeatures())) { auto fenceSync = reinterpret_cast(outSync); fenceSync->setIsCompositionFence(tInfo->m_isCompositionThread); } } // |rcClientWaitSyncKHR| implements |eglClientWaitSyncKHR| // on the guest through using the host's existing // |eglClientWaitSyncKHR| implementation, which is done // through the EmulatedEglFenceSync object. static EGLint rcClientWaitSyncKHR(uint64_t handle, EGLint flags, uint64_t timeout) { RenderThreadInfoGl* const tInfo = RenderThreadInfoGl::get(); if (!tInfo) { GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "Render thread GL not available."; } FrameBuffer *fb = FrameBuffer::getFB(); EGLSYNC_DPRINT("handle=0x%lx flags=0x%x timeout=%" PRIu64, handle, flags, timeout); EmulatedEglFenceSync* fenceSync = EmulatedEglFenceSync::getFromHandle(handle); if (!fenceSync) { EGLSYNC_DPRINT("fenceSync null, return condition satisfied"); return EGL_CONDITION_SATISFIED_KHR; } // Sometimes a gralloc-buffer-only thread is doing stuff with sync. // This happens all the time with YouTube videos in the browser. // In this case, create a context on the host just for syncing. if (!tInfo->currContext) { uint32_t gralloc_sync_cxt, gralloc_sync_surf; fb->createTrivialContext(0, // There is no context to share. &gralloc_sync_cxt, &gralloc_sync_surf); fb->bindContext(gralloc_sync_cxt, gralloc_sync_surf, gralloc_sync_surf); // This context is then cleaned up when the render thread exits. } return fenceSync->wait(timeout); } static void rcWaitSyncKHR(uint64_t handle, EGLint flags) { RenderThreadInfoGl* const tInfo = RenderThreadInfoGl::get(); if (!tInfo) { GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "Render thread GL not available."; } FrameBuffer *fb = FrameBuffer::getFB(); EGLSYNC_DPRINT("handle=0x%lx flags=0x%x", handle, flags); EmulatedEglFenceSync* fenceSync = EmulatedEglFenceSync::getFromHandle(handle); if (!fenceSync) { return; } // Sometimes a gralloc-buffer-only thread is doing stuff with sync. // This happens all the time with YouTube videos in the browser. // In this case, create a context on the host just for syncing. if (!tInfo->currContext) { uint32_t gralloc_sync_cxt, gralloc_sync_surf; fb->createTrivialContext(0, // There is no context to share. &gralloc_sync_cxt, &gralloc_sync_surf); fb->bindContext(gralloc_sync_cxt, gralloc_sync_surf, gralloc_sync_surf); // This context is then cleaned up when the render thread exits. } fenceSync->waitAsync(); } static int rcDestroySyncKHR(uint64_t handle) { EmulatedEglFenceSync* fenceSync = EmulatedEglFenceSync::getFromHandle(handle); if (!fenceSync) return 0; fenceSync->decRef(); return 0; } static void rcSetPuid(uint64_t puid) { if (puid == kInvalidPUID) { // The host process pipe implementation (GLProcessPipe) has been updated // to not generate a unique pipe id when running with virtio gpu and // instead send -1 to the guest. Ignore those requests as the PUID will // instead be the virtio gpu context id. return; } RenderThreadInfo *tInfo = RenderThreadInfo::get(); tInfo->m_puid = puid; } static int rcCompose(uint32_t bufferSize, void* buffer) { RenderThreadInfo *tInfo = RenderThreadInfo::get(); if (tInfo) tInfo->m_isCompositionThread = true; FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return -1; } return fb->compose(bufferSize, buffer, true); } static int rcComposeWithoutPost(uint32_t bufferSize, void* buffer) { RenderThreadInfo *tInfo = RenderThreadInfo::get(); if (tInfo) tInfo->m_isCompositionThread = true; FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return -1; } return fb->compose(bufferSize, buffer, false); } static int rcCreateDisplay(uint32_t* displayId) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return -1; } // Assume this API call always allocates a new displayId *displayId = FrameBuffer::s_invalidIdMultiDisplay; return fb->createDisplay(displayId); } static int rcCreateDisplayById(uint32_t displayId) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return -1; } return fb->createDisplay(displayId); } static int rcDestroyDisplay(uint32_t displayId) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return -1; } return fb->destroyDisplay(displayId); } static int rcSetDisplayColorBuffer(uint32_t displayId, uint32_t colorBuffer) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return -1; } return fb->setDisplayColorBuffer(displayId, colorBuffer); } static int rcGetDisplayColorBuffer(uint32_t displayId, uint32_t* colorBuffer) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return -1; } return fb->getDisplayColorBuffer(displayId, colorBuffer); } static int rcGetColorBufferDisplay(uint32_t colorBuffer, uint32_t* displayId) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return -1; } return fb->getColorBufferDisplay(colorBuffer, displayId); } static int rcGetDisplayPose(uint32_t displayId, int32_t* x, int32_t* y, uint32_t* w, uint32_t* h) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return -1; } return fb->getDisplayPose(displayId, x, y, w, h); } static int rcSetDisplayPose(uint32_t displayId, int32_t x, int32_t y, uint32_t w, uint32_t h) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return -1; } return fb->setDisplayPose(displayId, x, y, w, h); } static int rcSetDisplayPoseDpi(uint32_t displayId, int32_t x, int32_t y, uint32_t w, uint32_t h, uint32_t dpi) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return -1; } return fb->setDisplayPose(displayId, x, y, w, h, dpi); } static void rcReadColorBufferYUV(uint32_t colorBuffer, GLint x, GLint y, GLint width, GLint height, void* pixels, uint32_t pixels_size) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return; } fb->readColorBufferYUV(colorBuffer, x, y, width, height, pixels, pixels_size); } static int rcIsSyncSignaled(uint64_t handle) { EmulatedEglFenceSync* fenceSync = EmulatedEglFenceSync::getFromHandle(handle); if (!fenceSync) return 1; // assume destroyed => signaled return fenceSync->isSignaled() ? 1 : 0; } static void rcCreateColorBufferWithHandle( uint32_t width, uint32_t height, GLenum internalFormat, uint32_t handle) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return; } fb->createColorBufferWithHandle( width, height, internalFormat, FRAMEWORK_FORMAT_GL_COMPATIBLE, handle); } static uint32_t rcCreateBuffer2(uint64_t size, uint32_t memoryProperty) { FrameBuffer* fb = FrameBuffer::getFB(); if (!fb) { return 0; } return fb->createBuffer(size, memoryProperty); } static uint32_t rcCreateBuffer(uint32_t size) { return rcCreateBuffer2(size, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); } static void rcCloseBuffer(uint32_t buffer) { FrameBuffer* fb = FrameBuffer::getFB(); if (!fb) { return; } fb->closeBuffer(buffer); } static int rcSetColorBufferVulkanMode2(uint32_t colorBuffer, uint32_t mode, uint32_t memoryProperty) { #define VULKAN_MODE_VULKAN_ONLY 1 bool modeIsVulkanOnly = mode == VULKAN_MODE_VULKAN_ONLY; if (!vk::setColorBufferVulkanMode(colorBuffer, mode)) { fprintf(stderr, "%s: error: failed to set Vulkan mode for colorBuffer 0x%x\n", __func__, colorBuffer); return -1; } return 0; } static int rcSetColorBufferVulkanMode(uint32_t colorBuffer, uint32_t mode) { return rcSetColorBufferVulkanMode2(colorBuffer, mode, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); } static int32_t rcMapGpaToBufferHandle(uint32_t bufferHandle, uint64_t gpa) { int32_t result = vk::mapGpaToBufferHandle(bufferHandle, gpa); if (result < 0) { fprintf(stderr, "%s: error: failed to map gpa %" PRIx64 " to buffer handle 0x%x: %d\n", __func__, gpa, bufferHandle, result); } return result; } static int32_t rcMapGpaToBufferHandle2(uint32_t bufferHandle, uint64_t gpa, uint64_t size) { int32_t result = vk::mapGpaToBufferHandle(bufferHandle, gpa, size); if (result < 0) { fprintf(stderr, "%s: error: failed to map gpa %" PRIx64 " to buffer handle 0x%x: %d\n", __func__, gpa, bufferHandle, result); } return result; } static void rcFlushWindowColorBufferAsyncWithFrameNumber(uint32_t windowSurface, uint32_t frameNumber) { android::base::traceCounter("gfxstreamFrameNumber", (int64_t)frameNumber); rcFlushWindowColorBufferAsync(windowSurface); } static void rcSetTracingForPuid(uint64_t puid, uint32_t enable, uint64_t time) { if (enable) { android::base::setGuestTime(time); android::base::enableTracing(); } else { android::base::disableTracing(); } } static void rcMakeCurrentAsync(uint32_t context, uint32_t drawSurf, uint32_t readSurf) { AEMU_SCOPED_THRESHOLD_TRACE_CALL(); FrameBuffer* fb = FrameBuffer::getFB(); if (!fb) { return; } fb->bindContext(context, drawSurf, readSurf); } static void rcComposeAsync(uint32_t bufferSize, void* buffer) { RenderThreadInfo *tInfo = RenderThreadInfo::get(); if (tInfo) tInfo->m_isCompositionThread = true; FrameBuffer* fb = FrameBuffer::getFB(); if (!fb) { return; } fb->compose(bufferSize, buffer, true); } static void rcComposeAsyncWithoutPost(uint32_t bufferSize, void* buffer) { RenderThreadInfo *tInfo = RenderThreadInfo::get(); if (tInfo) tInfo->m_isCompositionThread = true; FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return; } fb->compose(bufferSize, buffer, false); } static void rcDestroySyncKHRAsync(uint64_t handle) { EmulatedEglFenceSync* fenceSync = EmulatedEglFenceSync::getFromHandle(handle); if (!fenceSync) return; fenceSync->decRef(); } static int rcReadColorBufferDMA(uint32_t colorBuffer, GLint x, GLint y, GLint width, GLint height, GLenum format, GLenum type, void* pixels, uint32_t pixels_size) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return -1; } fb->readColorBuffer(colorBuffer, x, y, width, height, format, type, pixels); return 0; } static int rcGetFBDisplayConfigsCount() { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return -1; } return fb->getDisplayConfigsCount(); } static int rcGetFBDisplayConfigsParam(int configId, GLint param) { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return -1; } return fb->getDisplayConfigsParam(configId, param); } static int rcGetFBDisplayActiveConfig() { FrameBuffer *fb = FrameBuffer::getFB(); if (!fb) { return -1; } return fb->getDisplayActiveConfig(); } static void rcSetProcessMetadata(char* key, RenderControlByte* valuePtr, uint32_t valueSize) { RenderThreadInfo* tInfo = RenderThreadInfo::get(); if (strcmp(key, "process_name") == 0) { // We know this is a c formatted string tInfo->m_processName = std::string((char*) valuePtr); } } static int rcGetHostExtensionsString(uint32_t bufferSize, void* buffer) { // TODO(b/233939967): split off host extensions from GL extensions. return rcGetGLString(GL_EXTENSIONS, buffer, bufferSize); } void initRenderControlContext(renderControl_decoder_context_t *dec) { dec->rcGetRendererVersion = rcGetRendererVersion; dec->rcGetEGLVersion = rcGetEGLVersion; dec->rcQueryEGLString = rcQueryEGLString; dec->rcGetGLString = rcGetGLString; dec->rcGetNumConfigs = rcGetNumConfigs; dec->rcGetConfigs = rcGetConfigs; dec->rcChooseConfig = rcChooseConfig; dec->rcGetFBParam = rcGetFBParam; dec->rcCreateContext = rcCreateContext; dec->rcDestroyContext = rcDestroyContext; dec->rcCreateWindowSurface = rcCreateWindowSurface; dec->rcDestroyWindowSurface = rcDestroyWindowSurface; dec->rcCreateColorBuffer = rcCreateColorBuffer; dec->rcOpenColorBuffer = rcOpenColorBuffer; dec->rcCloseColorBuffer = rcCloseColorBuffer; dec->rcSetWindowColorBuffer = rcSetWindowColorBuffer; dec->rcFlushWindowColorBuffer = rcFlushWindowColorBuffer; dec->rcMakeCurrent = rcMakeCurrent; dec->rcFBPost = rcFBPost; dec->rcFBSetSwapInterval = rcFBSetSwapInterval; dec->rcBindTexture = rcBindTexture; dec->rcBindRenderbuffer = rcBindRenderbuffer; dec->rcColorBufferCacheFlush = rcColorBufferCacheFlush; dec->rcReadColorBuffer = rcReadColorBuffer; dec->rcUpdateColorBuffer = rcUpdateColorBuffer; dec->rcOpenColorBuffer2 = rcOpenColorBuffer2; dec->rcCreateClientImage = rcCreateClientImage; dec->rcDestroyClientImage = rcDestroyClientImage; dec->rcSelectChecksumHelper = rcSelectChecksumHelper; dec->rcCreateSyncKHR = rcCreateSyncKHR; dec->rcClientWaitSyncKHR = rcClientWaitSyncKHR; dec->rcFlushWindowColorBufferAsync = rcFlushWindowColorBufferAsync; dec->rcDestroySyncKHR = rcDestroySyncKHR; dec->rcSetPuid = rcSetPuid; dec->rcUpdateColorBufferDMA = rcUpdateColorBufferDMA; dec->rcCreateColorBufferDMA = rcCreateColorBufferDMA; dec->rcWaitSyncKHR = rcWaitSyncKHR; dec->rcCompose = rcCompose; dec->rcCreateDisplay = rcCreateDisplay; dec->rcDestroyDisplay = rcDestroyDisplay; dec->rcSetDisplayColorBuffer = rcSetDisplayColorBuffer; dec->rcGetDisplayColorBuffer = rcGetDisplayColorBuffer; dec->rcGetColorBufferDisplay = rcGetColorBufferDisplay; dec->rcGetDisplayPose = rcGetDisplayPose; dec->rcSetDisplayPose = rcSetDisplayPose; dec->rcSetColorBufferVulkanMode = rcSetColorBufferVulkanMode; dec->rcReadColorBufferYUV = rcReadColorBufferYUV; dec->rcIsSyncSignaled = rcIsSyncSignaled; dec->rcCreateColorBufferWithHandle = rcCreateColorBufferWithHandle; dec->rcCreateBuffer = rcCreateBuffer; dec->rcCreateBuffer2 = rcCreateBuffer2; dec->rcCloseBuffer = rcCloseBuffer; dec->rcSetColorBufferVulkanMode2 = rcSetColorBufferVulkanMode2; dec->rcMapGpaToBufferHandle = rcMapGpaToBufferHandle; dec->rcMapGpaToBufferHandle2 = rcMapGpaToBufferHandle2; dec->rcFlushWindowColorBufferAsyncWithFrameNumber = rcFlushWindowColorBufferAsyncWithFrameNumber; dec->rcSetTracingForPuid = rcSetTracingForPuid; dec->rcMakeCurrentAsync = rcMakeCurrentAsync; dec->rcComposeAsync = rcComposeAsync; dec->rcDestroySyncKHRAsync = rcDestroySyncKHRAsync; dec->rcComposeWithoutPost = rcComposeWithoutPost; dec->rcComposeAsyncWithoutPost = rcComposeAsyncWithoutPost; dec->rcCreateDisplayById = rcCreateDisplayById; dec->rcSetDisplayPoseDpi = rcSetDisplayPoseDpi; dec->rcReadColorBufferDMA = rcReadColorBufferDMA; dec->rcGetFBDisplayConfigsCount = rcGetFBDisplayConfigsCount; dec->rcGetFBDisplayConfigsParam = rcGetFBDisplayConfigsParam; dec->rcGetFBDisplayActiveConfig = rcGetFBDisplayActiveConfig; dec->rcSetProcessMetadata = rcSetProcessMetadata; dec->rcGetHostExtensionsString = rcGetHostExtensionsString; } } // namespace gfxstream