diff options
Diffstat (limited to 'src/gfxstream/host/RendererImpl.cpp')
-rw-r--r-- | src/gfxstream/host/RendererImpl.cpp | 742 |
1 files changed, 742 insertions, 0 deletions
diff --git a/src/gfxstream/host/RendererImpl.cpp b/src/gfxstream/host/RendererImpl.cpp new file mode 100644 index 00000000000..33972b4a3bc --- /dev/null +++ b/src/gfxstream/host/RendererImpl.cpp @@ -0,0 +1,742 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "RendererImpl.h" + +#include <assert.h> + +#include <algorithm> +#include <utility> +#include <variant> + +#include "FrameBuffer.h" +#include "RenderChannelImpl.h" +#include "RenderThread.h" +#include "aemu/base/system/System.h" +#include "aemu/base/threads/WorkerThread.h" +#include "host-common/logging.h" +#include "snapshot/common.h" + +#if GFXSTREAM_ENABLE_HOST_GLES +#include "gl/EmulatedEglFenceSync.h" +#endif + +namespace gfxstream { + +// kUseSubwindowThread is used to determine whether the RenderWindow should use +// a separate thread to manage its subwindow GL/GLES context. +// For now, this feature is disabled entirely for the following +// reasons: +// +// - It must be disabled on Windows at all times, otherwise the main window +// becomes unresponsive after a few seconds of user interaction (e.g. trying +// to move it over the desktop). Probably due to the subtle issues around +// input on this platform (input-queue is global, message-queue is +// per-thread). Also, this messes considerably the display of the +// main window when running the executable under Wine. +// +// - On Linux/XGL and OSX/Cocoa, this used to be necessary to avoid corruption +// issues with the GL state of the main window when using the SDL UI. +// After the switch to Qt, this is no longer necessary and may actually cause +// undesired interactions between the UI thread and the RenderWindow thread: +// for example, in a multi-monitor setup the context might be recreated when +// dragging the window between monitors, triggering a Qt-specific callback +// in the context of RenderWindow thread, which will become blocked on the UI +// thread, which may in turn be blocked on something else. +static const bool kUseSubwindowThread = false; + +// This object manages the cleanup of guest process resources when the process +// exits. It runs the cleanup in a separate thread to never block the main +// render thread for a low-priority task. +class RendererImpl::ProcessCleanupThread { +public: + ProcessCleanupThread() + : mCleanupWorker([](Cmd cmd) { + using android::base::WorkerProcessingResult; + struct { + WorkerProcessingResult operator()(CleanProcessResources resources) { + FrameBuffer::getFB()->cleanupProcGLObjects(resources.puid); + // resources.resource are destroyed automatically when going out of the scope. + return WorkerProcessingResult::Continue; + } + WorkerProcessingResult operator()(Exit) { + return WorkerProcessingResult::Stop; + } + } visitor; + return std::visit(visitor, std::move(cmd)); + }) { + mCleanupWorker.start(); + } + + ~ProcessCleanupThread() { + mCleanupWorker.enqueue(Exit{}); + } + + void cleanup(uint64_t processId, std::unique_ptr<ProcessResources> resource) { + mCleanupWorker.enqueue(CleanProcessResources{ + .puid = processId, + .resource = std::move(resource), + }); + } + + void stop() { + mCleanupWorker.enqueue(Exit{}); + mCleanupWorker.join(); + } + + void waitForCleanup() { + mCleanupWorker.waitQueuedItems(); + } + +private: + struct CleanProcessResources { + uint64_t puid; + std::unique_ptr<ProcessResources> resource; + }; + struct Exit {}; + using Cmd = std::variant<CleanProcessResources, Exit>; + DISALLOW_COPY_AND_ASSIGN(ProcessCleanupThread); + + android::base::WorkerThread<Cmd> mCleanupWorker; +}; + +RendererImpl::RendererImpl() { + mCleanupThread.reset(new ProcessCleanupThread()); +} + +RendererImpl::~RendererImpl() { + stop(true); + // We can't finish until the loader render thread has + // completed else can get a crash at the end of the destructor. + if (mLoaderRenderThread) { + mLoaderRenderThread->wait(); + } + mRenderWindow.reset(); +} + +bool RendererImpl::initialize(int width, int height, bool useSubWindow, bool egl2egl) { + if (android::base::getEnvironmentVariable("ANDROID_EMUGL_VERBOSE") == "1") { + // base_enable_verbose_logs(); + } + + if (mRenderWindow) { + return false; + } + + std::unique_ptr<RenderWindow> renderWindow(new RenderWindow( + width, height, kUseSubwindowThread, useSubWindow, egl2egl)); + if (!renderWindow) { + ERR("Could not create rendering window class\n"); + GL_LOG("Could not create rendering window class"); + return false; + } + if (!renderWindow->isValid()) { + ERR("Could not initialize emulated framebuffer\n"); + return false; + } + + mRenderWindow = std::move(renderWindow); + GL_LOG("OpenGL renderer initialized successfully"); + + // This render thread won't do anything but will only preload resources + // for the real threads to start faster. + mLoaderRenderThread.reset(new RenderThread(nullptr)); + mLoaderRenderThread->start(); + + return true; +} + +void RendererImpl::stop(bool wait) { + android::base::AutoLock lock(mChannelsLock); + mStopped = true; + auto channels = std::move(mChannels); + lock.unlock(); + + if (const auto fb = FrameBuffer::getFB()) { + fb->setShuttingDown(); + } + for (const auto& c : channels) { + c->stopFromHost(); + } + // We're stopping the renderer, so there's no need to clean up resources + // of some pending processes: we'll destroy everything soon. + mCleanupThread->stop(); + + mStoppedChannels.insert(mStoppedChannels.end(), + std::make_move_iterator(channels.begin()), + std::make_move_iterator(channels.end())); + + if (!wait) { + return; + } + + // Each render channel is referenced in the corresponing pipe object, so + // even if we clear the |channels| vector they could still be alive + // for a while. This means we need to make sure to wait for render thread + // exit explicitly. + for (const auto& c : mStoppedChannels) { + c->renderThread()->wait(); + } + mCleanupThread->waitForCleanup(); + mStoppedChannels.clear(); +} + +void RendererImpl::finish() { + { + android::base::AutoLock lock(mChannelsLock); + mRenderWindow->setPaused(true); + } + cleanupRenderThreads(); + { + android::base::AutoLock lock(mChannelsLock); + mRenderWindow->setPaused(false); + } +} + +void RendererImpl::cleanupRenderThreads() { + android::base::AutoLock lock(mChannelsLock); + const auto channels = std::move(mChannels); + assert(mChannels.empty()); + lock.unlock(); + for (const auto& c : channels) { + // Please DO NOT notify the guest about this event (DO NOT call + // stopFromHost() ), because this is used to kill old threads when + // loading from a snapshot, and the newly loaded guest should not + // be notified for those behavior. + c->stop(); + } + for (const auto& c : channels) { + c->renderThread()->wait(); + } +} + +void RendererImpl::waitForProcessCleanup() { + mCleanupThread->waitForCleanup(); + // Recreate it to make sure we've started from scratch and that we've + // finished all in-progress cleanups as well. + mCleanupThread.reset(new ProcessCleanupThread()); +} + +RenderChannelPtr RendererImpl::createRenderChannel( + android::base::Stream* loadStream, uint32_t virtioGpuContextId) { + const auto channel = + std::make_shared<RenderChannelImpl>(loadStream, virtioGpuContextId); + { + android::base::AutoLock lock(mChannelsLock); + + if (mStopped) { + return nullptr; + } + + // Clean up the stopped channels. + mChannels.erase( + std::remove_if(mChannels.begin(), mChannels.end(), + [](const std::shared_ptr<RenderChannelImpl>& c) { + return c->renderThread()->isFinished(); + }), + mChannels.end()); + mChannels.emplace_back(channel); + + // Take the time to check if our loader thread is done as well. + if (mLoaderRenderThread && mLoaderRenderThread->isFinished()) { + mLoaderRenderThread->wait(); + mLoaderRenderThread.reset(); + } + + GL_LOG("Started new RenderThread (total %" PRIu64 ") @%p", + static_cast<uint64_t>(mChannels.size()), channel->renderThread()); + } + + return channel; +} + +void RendererImpl::addListener(FrameBufferChangeEventListener* listener) { + mRenderWindow->addListener(listener); +} + +void RendererImpl::removeListener(FrameBufferChangeEventListener* listener) { + mRenderWindow->removeListener(listener); +} + +void* RendererImpl::addressSpaceGraphicsConsumerCreate( + struct asg_context context, + android::base::Stream* loadStream, + android::emulation::asg::ConsumerCallbacks callbacks, + uint32_t contextId, uint32_t capsetId, + std::optional<std::string> nameOpt) { + auto thread = new RenderThread(context, loadStream, callbacks, contextId, + capsetId, std::move(nameOpt)); + thread->start(); + android::base::AutoLock lock(mAddressSpaceRenderThreadLock); + mAddressSpaceRenderThreads.emplace(thread); + return (void*)thread; +} + +void RendererImpl::addressSpaceGraphicsConsumerDestroy(void* consumer) { + RenderThread* thread = (RenderThread*)consumer; + { + android::base::AutoLock lock(mAddressSpaceRenderThreadLock); + mAddressSpaceRenderThreads.erase(thread); + } + thread->wait(); + delete thread; +} + +void RendererImpl::addressSpaceGraphicsConsumerPreSave(void* consumer) { + RenderThread* thread = (RenderThread*)consumer; + thread->pausePreSnapshot(); +} + +void RendererImpl::addressSpaceGraphicsConsumerSave(void* consumer, android::base::Stream* stream) { + RenderThread* thread = (RenderThread*)consumer; + thread->save(stream); +} + +void RendererImpl::addressSpaceGraphicsConsumerPostSave(void* consumer) { + RenderThread* thread = (RenderThread*)consumer; + thread->resume(true); +} + +void RendererImpl::addressSpaceGraphicsConsumerRegisterPostLoadRenderThread(void* consumer) { + RenderThread* thread = (RenderThread*)consumer; + mAdditionalPostLoadRenderThreads.push_back(thread); +} + +void RendererImpl::pauseAllPreSave() { + { + android::base::AutoLock lock(mChannelsLock); + if (mStopped) { + return; + } + for (const auto& c : mChannels) { + c->renderThread()->pausePreSnapshot(); + } + } + { + android::base::AutoLock lock(mAddressSpaceRenderThreadLock); + for (const auto& thread : mAddressSpaceRenderThreads) { + thread->pausePreSnapshot(); + } + } + waitForProcessCleanup(); +} + +void RendererImpl::resumeAll(bool waitForSave) { + { + android::base::AutoLock lock(mAddressSpaceRenderThreadLock); + for (const auto t : mAdditionalPostLoadRenderThreads) { + t->resume(waitForSave); + } + } + { + android::base::AutoLock lock(mChannelsLock); + if (mStopped) { + return; + } + for (const auto& c : mChannels) { + c->renderThread()->resume(waitForSave); + } + for (const auto& thread : mAddressSpaceRenderThreads) { + thread->resume(waitForSave); + } + mAdditionalPostLoadRenderThreads.clear(); + } + + repaintOpenGLDisplay(); +} + +void RendererImpl::save(android::base::Stream* stream, + const android::snapshot::ITextureSaverPtr& textureSaver) { + stream->putByte(mStopped); + if (mStopped) { + return; + } + auto fb = FrameBuffer::getFB(); + assert(fb); + fb->onSave(stream, textureSaver); +} + +bool RendererImpl::load(android::base::Stream* stream, + const android::snapshot::ITextureLoaderPtr& textureLoader) { + +#ifdef SNAPSHOT_PROFILE + android::base::System::Duration startTime = + android::base::System::get()->getUnixTimeUs(); +#endif + waitForProcessCleanup(); +#ifdef SNAPSHOT_PROFILE + printf("Previous session cleanup time: %lld ms\n", + (long long)(android::base::System::get() + ->getUnixTimeUs() - + startTime) / + 1000); +#endif + + mStopped = stream->getByte(); + if (mStopped) { + return true; + } + auto fb = FrameBuffer::getFB(); + assert(fb); + + bool res = true; + + res = fb->onLoad(stream, textureLoader); +#if GFXSTREAM_ENABLE_HOST_GLES + gl::EmulatedEglFenceSync::onLoad(stream); +#endif + + return res; +} + +void RendererImpl::fillGLESUsages(android_studio::EmulatorGLESUsages* usages) { + auto fb = FrameBuffer::getFB(); +#if GFXSTREAM_ENABLE_HOST_GLES + if (fb) fb->fillGLESUsages(usages); +#endif +} + +int RendererImpl::getScreenshot(unsigned int nChannels, unsigned int* width, unsigned int* height, + uint8_t* pixels, size_t* cPixels, int displayId = 0, + int desiredWidth = 0, int desiredHeight = 0, + int desiredRotation = 0, Rect rect = {{0, 0}, {0, 0}}) { + auto fb = FrameBuffer::getFB(); + if (fb) { + return fb->getScreenshot(nChannels, width, height, pixels, cPixels, + displayId, desiredWidth, desiredHeight, + desiredRotation, rect); + } + *cPixels = 0; + return -1; +} + +void RendererImpl::setMultiDisplay(uint32_t id, + int32_t x, + int32_t y, + uint32_t w, + uint32_t h, + uint32_t dpi, + bool add) { + auto fb = FrameBuffer::getFB(); + if (fb) { + if (add) { + fb->createDisplay(&id); + fb->setDisplayPose(id, x, y, w, h, dpi); + } else { + fb->destroyDisplay(id); + } + } +} + +void RendererImpl::setMultiDisplayColorBuffer(uint32_t id, uint32_t cb) { + auto fb = FrameBuffer::getFB(); + if (fb) { + fb->setDisplayColorBuffer(id, cb); + } +} + +RendererImpl::HardwareStrings RendererImpl::getHardwareStrings() { + assert(mRenderWindow); + + const char* vendor = nullptr; + const char* renderer = nullptr; + const char* version = nullptr; + if (!mRenderWindow->getHardwareStrings(&vendor, &renderer, &version)) { + return {}; + } + HardwareStrings res; + res.vendor = vendor ? vendor : ""; + res.renderer = renderer ? renderer : ""; + res.version = version ? version : ""; + return res; +} + +void RendererImpl::setPostCallback(RendererImpl::OnPostCallback onPost, + void* context, + bool useBgraReadback, + uint32_t displayId) { + assert(mRenderWindow); + mRenderWindow->setPostCallback(onPost, context, displayId, useBgraReadback); +} + +bool RendererImpl::asyncReadbackSupported() { + assert(mRenderWindow); + return mRenderWindow->asyncReadbackSupported(); +} + +RendererImpl::ReadPixelsCallback +RendererImpl::getReadPixelsCallback() { + assert(mRenderWindow); + return mRenderWindow->getReadPixelsCallback(); +} + +RendererImpl::FlushReadPixelPipeline +RendererImpl::getFlushReadPixelPipeline() { + assert(mRenderWindow); + return mRenderWindow->getFlushReadPixelPipeline(); +} + +bool RendererImpl::showOpenGLSubwindow(FBNativeWindowType window, + int wx, + int wy, + int ww, + int wh, + int fbw, + int fbh, + float dpr, + float zRot, + bool deleteExisting, + bool hideWindow) { + assert(mRenderWindow); + return mRenderWindow->setupSubWindow(window, wx, wy, ww, wh, fbw, fbh, dpr, + zRot, deleteExisting, hideWindow); +} + +bool RendererImpl::destroyOpenGLSubwindow() { + assert(mRenderWindow); + return mRenderWindow->removeSubWindow(); +} + +void RendererImpl::setOpenGLDisplayRotation(float zRot) { + assert(mRenderWindow); + mRenderWindow->setRotation(zRot); +} + +void RendererImpl::setOpenGLDisplayTranslation(float px, float py) { + assert(mRenderWindow); + mRenderWindow->setTranslation(px, py); +} + +void RendererImpl::repaintOpenGLDisplay() { + assert(mRenderWindow); + mRenderWindow->repaint(); +} + +bool RendererImpl::hasGuestPostedAFrame() { + if (mRenderWindow) { + return mRenderWindow->hasGuestPostedAFrame(); + } + return false; +} + +void RendererImpl::resetGuestPostedAFrame() { + if (mRenderWindow) { + mRenderWindow->resetGuestPostedAFrame(); + } +} + +void RendererImpl::setScreenMask(int width, int height, const unsigned char* rgbaData) { + assert(mRenderWindow); + mRenderWindow->setScreenMask(width, height, rgbaData); +} + +void RendererImpl::onGuestGraphicsProcessCreate(uint64_t puid) { + FrameBuffer::getFB()->createGraphicsProcessResources(puid); +} + +void RendererImpl::cleanupProcGLObjects(uint64_t puid) { + std::unique_ptr<ProcessResources> resource = + FrameBuffer::getFB()->removeGraphicsProcessResources(puid); + mCleanupThread->cleanup(puid, std::move(resource)); +} + +static struct AndroidVirtioGpuOps sVirtioGpuOps = { + .create_buffer_with_handle = + [](uint64_t size, uint32_t handle) { + FrameBuffer::getFB()->createBufferWithHandle(size, handle); + }, + .create_color_buffer_with_handle = + [](uint32_t width, uint32_t height, uint32_t format, uint32_t fwkFormat, uint32_t handle) { + FrameBuffer::getFB()->createColorBufferWithHandle(width, height, (GLenum)format, + (FrameworkFormat)fwkFormat, handle); + }, + .open_color_buffer = [](uint32_t handle) { FrameBuffer::getFB()->openColorBuffer(handle); }, + .close_buffer = [](uint32_t handle) { FrameBuffer::getFB()->closeBuffer(handle); }, + .close_color_buffer = [](uint32_t handle) { FrameBuffer::getFB()->closeColorBuffer(handle); }, + .update_buffer = + [](uint32_t handle, uint64_t offset, uint64_t size, void* bytes) { + FrameBuffer::getFB()->updateBuffer(handle, offset, size, bytes); + }, + .update_color_buffer = + [](uint32_t handle, int x, int y, int width, int height, uint32_t format, uint32_t type, + void* pixels) { + FrameBuffer::getFB()->updateColorBuffer(handle, x, y, width, height, format, type, + pixels); + }, + .read_buffer = + [](uint32_t handle, uint64_t offset, uint64_t size, void* bytes) { + FrameBuffer::getFB()->readBuffer(handle, offset, size, bytes); + }, + .read_color_buffer = + [](uint32_t handle, int x, int y, int width, int height, uint32_t format, uint32_t type, + void* pixels) { + FrameBuffer::getFB()->readColorBuffer(handle, x, y, width, height, format, type, + pixels); + }, + .read_color_buffer_yuv = + [](uint32_t handle, int x, int y, int width, int height, void* pixels, + uint32_t pixels_size) { + FrameBuffer::getFB()->readColorBufferYUV(handle, x, y, width, height, pixels, + pixels_size); + }, + .post_color_buffer = [](uint32_t handle) { FrameBuffer::getFB()->post(handle); }, + .async_post_color_buffer = + [](uint32_t handle, CpuCompletionCallback cb) { + FrameBuffer::getFB()->postWithCallback(handle, cb); + }, + .repost = []() { FrameBuffer::getFB()->repost(); }, +#if GFXSTREAM_ENABLE_HOST_GLES + .create_yuv_textures = + [](uint32_t type, uint32_t count, int width, int height, uint32_t* output) { + FrameBuffer::getFB()->createYUVTextures(type, count, width, height, output); + }, + .destroy_yuv_textures = + [](uint32_t type, uint32_t count, uint32_t* textures) { + FrameBuffer::getFB()->destroyYUVTextures(type, count, textures); + }, + .update_yuv_textures = + [](uint32_t type, uint32_t* textures, void* privData, void* func) { + FrameBuffer::getFB()->updateYUVTextures(type, textures, privData, func); + }, + .swap_textures_and_update_color_buffer = + [](uint32_t colorbufferhandle, int x, int y, int width, int height, uint32_t format, + uint32_t type, uint32_t texture_type, uint32_t* textures, void* metadata) { + FrameBuffer::getFB()->swapTexturesAndUpdateColorBuffer( + colorbufferhandle, x, y, width, height, format, type, texture_type, textures); + }, +#endif + .get_last_posted_color_buffer = + []() { return FrameBuffer::getFB()->getLastPostedColorBuffer(); }, +#if GFXSTREAM_ENABLE_HOST_GLES + .bind_color_buffer_to_texture = + [](uint32_t handle) { FrameBuffer::getFB()->bindColorBufferToTexture2(handle); }, + .get_global_egl_context = []() { return FrameBuffer::getFB()->getGlobalEGLContext(); }, + .wait_for_gpu = [](uint64_t eglsync) { FrameBuffer::getFB()->waitForGpu(eglsync); }, +#endif + .wait_for_gpu_vulkan = + [](uint64_t device, uint64_t fence) { + FrameBuffer::getFB()->waitForGpuVulkan(device, fence); + }, + .set_guest_managed_color_buffer_lifetime = + [](bool guestManaged) { + FrameBuffer::getFB()->setGuestManagedColorBufferLifetime(guestManaged); + }, +#if GFXSTREAM_ENABLE_HOST_GLES + .async_wait_for_gpu_with_cb = + [](uint64_t eglsync, FenceCompletionCallback cb) { + FrameBuffer::getFB()->asyncWaitForGpuWithCb(eglsync, cb); + }, +#endif + .async_wait_for_gpu_vulkan_with_cb = + [](uint64_t device, uint64_t fence, FenceCompletionCallback cb) { + FrameBuffer::getFB()->asyncWaitForGpuVulkanWithCb(device, fence, cb); + }, + .async_wait_for_gpu_vulkan_qsri_with_cb = + [](uint64_t image, FenceCompletionCallback cb) { + FrameBuffer::getFB()->asyncWaitForGpuVulkanQsriWithCb(image, cb); + }, + .wait_for_gpu_vulkan_qsri = + [](uint64_t image) { FrameBuffer::getFB()->waitForGpuVulkanQsri(image); }, + .update_color_buffer_from_framework_format = + [](uint32_t handle, int x, int y, int width, int height, uint32_t fwkFormat, + uint32_t format, uint32_t type, void* pixels, void* pMetadata) { + FrameBuffer::getFB()->updateColorBufferFromFrameworkFormat( + handle, x, y, width, height, (FrameworkFormat)fwkFormat, format, type, pixels, + pMetadata); + }, + .platform_import_resource = + [](uint32_t handle, uint32_t info, void* resource) { + return FrameBuffer::getFB()->platformImportResource(handle, info, resource); + }, + .platform_resource_info = + [](uint32_t handle, int32_t* width, int32_t* height, int32_t* internal_format) { + return FrameBuffer::getFB()->getColorBufferInfo(handle, width, height, internal_format); + }, +#if GFXSTREAM_ENABLE_HOST_GLES + .platform_create_shared_egl_context = + []() { return FrameBuffer::getFB()->platformCreateSharedEglContext(); }, + .platform_destroy_shared_egl_context = + [](void* context) { + return FrameBuffer::getFB()->platformDestroySharedEglContext(context); + }, +#endif +}; + +struct AndroidVirtioGpuOps* RendererImpl::getVirtioGpuOps() { + return &sVirtioGpuOps; +} + +void RendererImpl::snapshotOperationCallback(int op, int stage) { + using namespace android::snapshot; + switch (op) { + case SNAPSHOTTER_OPERATION_LOAD: + if (stage == SNAPSHOTTER_STAGE_START) { +#ifdef SNAPSHOT_PROFILE + android::base::System::Duration startTime = + android::base::System::get()->getUnixTimeUs(); +#endif + mRenderWindow->setPaused(true); + cleanupRenderThreads(); +#ifdef SNAPSHOT_PROFILE + printf("Previous session suspend time: %lld ms\n", + (long long)(android::base::System::get() + ->getUnixTimeUs() - + startTime) / + 1000); +#endif + } + if (stage == SNAPSHOTTER_STAGE_END) { + mRenderWindow->setPaused(false); + } + break; + default: + break; + } +} + +void RendererImpl::setVsyncHz(int vsyncHz) { + if (mRenderWindow) { + mRenderWindow->setVsyncHz(vsyncHz); + } +} + +void RendererImpl::setDisplayConfigs(int configId, int w, int h, + int dpiX, int dpiY) { + if (mRenderWindow) { + mRenderWindow->setDisplayConfigs(configId, w, h, dpiX, dpiY); + } +} + +void RendererImpl::setDisplayActiveConfig(int configId) { + if (mRenderWindow) { + mRenderWindow->setDisplayActiveConfig(configId); + } +} + +const void* RendererImpl::getEglDispatch() { +#if GFXSTREAM_ENABLE_HOST_GLES + return FrameBuffer::getFB()->getEglDispatch(); +#else + return nullptr; +#endif +} + +const void* RendererImpl::getGles2Dispatch() { +#if GFXSTREAM_ENABLE_HOST_GLES + return FrameBuffer::getFB()->getGles2Dispatch(); +#else + return nullptr; +#endif +} + +} // namespace gfxstream |