blob: 79cb6c0dd92fcbaa8db3a5240cc2e6e1fa4d1d3b [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "gpu/command_buffer/service/in_process_command_buffer.h"
#include <queue>
#include <set>
#include <utility>
#include <GLES2/gl2.h>
#ifndef GL_GLEXT_PROTOTYPES
#define GL_GLEXT_PROTOTYPES 1
#endif
#include <GLES2/gl2ext.h>
#include <GLES2/gl2extchromium.h>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/sequence_checker.h"
#include "base/synchronization/condition_variable.h"
#include "base/threading/thread.h"
#include "gpu/command_buffer/client/gpu_memory_buffer_factory.h"
#include "gpu/command_buffer/service/command_buffer_service.h"
#include "gpu/command_buffer/service/context_group.h"
#include "gpu/command_buffer/service/gl_context_virtual.h"
#include "gpu/command_buffer/service/gpu_control_service.h"
#include "gpu/command_buffer/service/gpu_scheduler.h"
#include "gpu/command_buffer/service/image_manager.h"
#include "gpu/command_buffer/service/mailbox_manager.h"
#include "gpu/command_buffer/service/transfer_buffer_manager.h"
#include "ui/gfx/size.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_image.h"
#include "ui/gl/gl_share_group.h"
#if defined(OS_ANDROID)
#include "gpu/command_buffer/service/stream_texture_manager_in_process_android.h"
#include "ui/gl/android/surface_texture.h"
#endif
namespace gpu {
namespace {
static GpuMemoryBufferFactory* g_gpu_memory_buffer_factory = NULL;
template <typename T>
static void RunTaskWithResult(base::Callback<T(void)> task,
T* result,
base::WaitableEvent* completion) {
*result = task.Run();
completion->Signal();
}
class GpuInProcessThread
: public base::Thread,
public InProcessCommandBuffer::Service,
public base::RefCountedThreadSafe<GpuInProcessThread> {
public:
GpuInProcessThread();
virtual void AddRef() const OVERRIDE {
base::RefCountedThreadSafe<GpuInProcessThread>::AddRef();
}
virtual void Release() const OVERRIDE {
base::RefCountedThreadSafe<GpuInProcessThread>::Release();
}
virtual void ScheduleTask(const base::Closure& task) OVERRIDE;
virtual void ScheduleIdleWork(const base::Closure& callback) OVERRIDE;
virtual bool UseVirtualizedGLContexts() OVERRIDE { return false; }
virtual scoped_refptr<gles2::ShaderTranslatorCache> shader_translator_cache()
OVERRIDE;
private:
virtual ~GpuInProcessThread();
friend class base::RefCountedThreadSafe<GpuInProcessThread>;
scoped_refptr<gpu::gles2::ShaderTranslatorCache> shader_translator_cache_;
DISALLOW_COPY_AND_ASSIGN(GpuInProcessThread);
};
GpuInProcessThread::GpuInProcessThread() : base::Thread("GpuThread") {
Start();
}
GpuInProcessThread::~GpuInProcessThread() {
Stop();
}
void GpuInProcessThread::ScheduleTask(const base::Closure& task) {
message_loop()->PostTask(FROM_HERE, task);
}
void GpuInProcessThread::ScheduleIdleWork(const base::Closure& callback) {
message_loop()->PostDelayedTask(
FROM_HERE, callback, base::TimeDelta::FromMilliseconds(5));
}
scoped_refptr<gles2::ShaderTranslatorCache>
GpuInProcessThread::shader_translator_cache() {
if (!shader_translator_cache_.get())
shader_translator_cache_ = new gpu::gles2::ShaderTranslatorCache;
return shader_translator_cache_;
}
base::LazyInstance<std::set<InProcessCommandBuffer*> > default_thread_clients_ =
LAZY_INSTANCE_INITIALIZER;
base::LazyInstance<base::Lock> default_thread_clients_lock_ =
LAZY_INSTANCE_INITIALIZER;
class ScopedEvent {
public:
ScopedEvent(base::WaitableEvent* event) : event_(event) {}
~ScopedEvent() { event_->Signal(); }
private:
base::WaitableEvent* event_;
};
class SyncPointManager {
public:
SyncPointManager();
~SyncPointManager();
uint32 GenerateSyncPoint();
void RetireSyncPoint(uint32 sync_point);
bool IsSyncPointPassed(uint32 sync_point);
void WaitSyncPoint(uint32 sync_point);
private:
// This lock protects access to pending_sync_points_ and next_sync_point_ and
// is used with the ConditionVariable to signal when a sync point is retired.
base::Lock lock_;
std::set<uint32> pending_sync_points_;
uint32 next_sync_point_;
base::ConditionVariable cond_var_;
};
SyncPointManager::SyncPointManager() : next_sync_point_(1), cond_var_(&lock_) {}
SyncPointManager::~SyncPointManager() {
DCHECK_EQ(pending_sync_points_.size(), 0U);
}
uint32 SyncPointManager::GenerateSyncPoint() {
base::AutoLock lock(lock_);
uint32 sync_point = next_sync_point_++;
DCHECK_EQ(pending_sync_points_.count(sync_point), 0U);
pending_sync_points_.insert(sync_point);
return sync_point;
}
void SyncPointManager::RetireSyncPoint(uint32 sync_point) {
base::AutoLock lock(lock_);
DCHECK(pending_sync_points_.count(sync_point));
pending_sync_points_.erase(sync_point);
cond_var_.Broadcast();
}
bool SyncPointManager::IsSyncPointPassed(uint32 sync_point) {
base::AutoLock lock(lock_);
return pending_sync_points_.count(sync_point) == 0;
}
void SyncPointManager::WaitSyncPoint(uint32 sync_point) {
base::AutoLock lock(lock_);
while (pending_sync_points_.count(sync_point)) {
cond_var_.Wait();
}
}
base::LazyInstance<SyncPointManager> g_sync_point_manager =
LAZY_INSTANCE_INITIALIZER;
bool WaitSyncPoint(uint32 sync_point) {
g_sync_point_manager.Get().WaitSyncPoint(sync_point);
return true;
}
} // anonyous namespace
InProcessCommandBuffer::Service::Service() {}
InProcessCommandBuffer::Service::~Service() {}
scoped_refptr<InProcessCommandBuffer::Service>
InProcessCommandBuffer::GetDefaultService() {
base::AutoLock lock(default_thread_clients_lock_.Get());
scoped_refptr<Service> service;
if (!default_thread_clients_.Get().empty()) {
InProcessCommandBuffer* other = *default_thread_clients_.Get().begin();
service = other->service_;
DCHECK(service.get());
} else {
service = new GpuInProcessThread;
}
return service;
}
InProcessCommandBuffer::InProcessCommandBuffer(
const scoped_refptr<Service>& service)
: context_lost_(false),
last_put_offset_(-1),
flush_event_(false, false),
service_(service.get() ? service : GetDefaultService()),
gpu_thread_weak_ptr_factory_(this) {
if (!service) {
base::AutoLock lock(default_thread_clients_lock_.Get());
default_thread_clients_.Get().insert(this);
}
}
InProcessCommandBuffer::~InProcessCommandBuffer() {
Destroy();
base::AutoLock lock(default_thread_clients_lock_.Get());
default_thread_clients_.Get().erase(this);
}
void InProcessCommandBuffer::OnResizeView(gfx::Size size, float scale_factor) {
CheckSequencedThread();
DCHECK(!surface_->IsOffscreen());
surface_->Resize(size);
}
bool InProcessCommandBuffer::MakeCurrent() {
CheckSequencedThread();
command_buffer_lock_.AssertAcquired();
if (!context_lost_ && decoder_->MakeCurrent())
return true;
DLOG(ERROR) << "Context lost because MakeCurrent failed.";
command_buffer_->SetContextLostReason(decoder_->GetContextLostReason());
command_buffer_->SetParseError(gpu::error::kLostContext);
return false;
}
void InProcessCommandBuffer::PumpCommands() {
CheckSequencedThread();
command_buffer_lock_.AssertAcquired();
if (!MakeCurrent())
return;
gpu_scheduler_->PutChanged();
}
bool InProcessCommandBuffer::GetBufferChanged(int32 transfer_buffer_id) {
CheckSequencedThread();
command_buffer_lock_.AssertAcquired();
command_buffer_->SetGetBuffer(transfer_buffer_id);
return true;
}
bool InProcessCommandBuffer::Initialize(
scoped_refptr<gfx::GLSurface> surface,
bool is_offscreen,
gfx::AcceleratedWidget window,
const gfx::Size& size,
const std::vector<int32>& attribs,
gfx::GpuPreference gpu_preference,
const base::Closure& context_lost_callback,
InProcessCommandBuffer* share_group) {
DCHECK(!share_group || service_ == share_group->service_);
context_lost_callback_ = WrapCallback(context_lost_callback);
if (surface) {
// GPU thread must be the same as client thread due to GLSurface not being
// thread safe.
sequence_checker_.reset(new base::SequenceChecker);
surface_ = surface;
}
gpu::Capabilities capabilities;
InitializeOnGpuThreadParams params(is_offscreen,
window,
size,
attribs,
gpu_preference,
&capabilities,
share_group);
base::Callback<bool(void)> init_task =
base::Bind(&InProcessCommandBuffer::InitializeOnGpuThread,
base::Unretained(this),
params);
base::WaitableEvent completion(true, false);
bool result = false;
QueueTask(
base::Bind(&RunTaskWithResult<bool>, init_task, &result, &completion));
completion.Wait();
if (result) {
capabilities_ = capabilities;
capabilities_.map_image =
capabilities_.map_image && g_gpu_memory_buffer_factory;
}
return result;
}
bool InProcessCommandBuffer::InitializeOnGpuThread(
const InitializeOnGpuThreadParams& params) {
CheckSequencedThread();
gpu_thread_weak_ptr_ = gpu_thread_weak_ptr_factory_.GetWeakPtr();
DCHECK(params.size.width() >= 0 && params.size.height() >= 0);
TransferBufferManager* manager = new TransferBufferManager();
transfer_buffer_manager_.reset(manager);
manager->Initialize();
scoped_ptr<CommandBufferService> command_buffer(
new CommandBufferService(transfer_buffer_manager_.get()));
command_buffer->SetPutOffsetChangeCallback(base::Bind(
&InProcessCommandBuffer::PumpCommands, gpu_thread_weak_ptr_));
command_buffer->SetParseErrorCallback(base::Bind(
&InProcessCommandBuffer::OnContextLost, gpu_thread_weak_ptr_));
if (!command_buffer->Initialize()) {
LOG(ERROR) << "Could not initialize command buffer.";
DestroyOnGpuThread();
return false;
}
gl_share_group_ = params.context_group
? params.context_group->gl_share_group_.get()
: new gfx::GLShareGroup;
#if defined(OS_ANDROID)
stream_texture_manager_.reset(new StreamTextureManagerInProcess);
#endif
bool bind_generates_resource = false;
decoder_.reset(gles2::GLES2Decoder::Create(
params.context_group
? params.context_group->decoder_->GetContextGroup()
: new gles2::ContextGroup(NULL,
NULL,
NULL,
service_->shader_translator_cache(),
NULL,
bind_generates_resource)));
gpu_scheduler_.reset(
new GpuScheduler(command_buffer.get(), decoder_.get(), decoder_.get()));
command_buffer->SetGetBufferChangeCallback(base::Bind(
&GpuScheduler::SetGetBuffer, base::Unretained(gpu_scheduler_.get())));
command_buffer_ = command_buffer.Pass();
decoder_->set_engine(gpu_scheduler_.get());
if (!surface_) {
if (params.is_offscreen)
surface_ = gfx::GLSurface::CreateOffscreenGLSurface(params.size);
else
surface_ = gfx::GLSurface::CreateViewGLSurface(params.window);
}
if (!surface_.get()) {
LOG(ERROR) << "Could not create GLSurface.";
DestroyOnGpuThread();
return false;
}
if (service_->UseVirtualizedGLContexts()) {
context_ = gl_share_group_->GetSharedContext();
if (!context_.get()) {
context_ = gfx::GLContext::CreateGLContext(
gl_share_group_.get(), surface_.get(), params.gpu_preference);
gl_share_group_->SetSharedContext(context_.get());
}
context_ = new GLContextVirtual(
gl_share_group_.get(), context_.get(), decoder_->AsWeakPtr());
if (context_->Initialize(surface_.get(), params.gpu_preference)) {
VLOG(1) << "Created virtual GL context.";
} else {
context_ = NULL;
}
} else {
context_ = gfx::GLContext::CreateGLContext(
gl_share_group_.get(), surface_.get(), params.gpu_preference);
}
if (!context_.get()) {
LOG(ERROR) << "Could not create GLContext.";
DestroyOnGpuThread();
return false;
}
if (!context_->MakeCurrent(surface_.get())) {
LOG(ERROR) << "Could not make context current.";
DestroyOnGpuThread();
return false;
}
gles2::DisallowedFeatures disallowed_features;
disallowed_features.gpu_memory_manager = true;
if (!decoder_->Initialize(surface_,
context_,
params.is_offscreen,
params.size,
disallowed_features,
params.attribs)) {
LOG(ERROR) << "Could not initialize decoder.";
DestroyOnGpuThread();
return false;
}
*params.capabilities = decoder_->GetCapabilities();
gpu_control_.reset(
new GpuControlService(decoder_->GetContextGroup()->image_manager(),
decoder_->GetQueryManager()));
if (!params.is_offscreen) {
decoder_->SetResizeCallback(base::Bind(
&InProcessCommandBuffer::OnResizeView, gpu_thread_weak_ptr_));
}
decoder_->SetWaitSyncPointCallback(base::Bind(&WaitSyncPoint));
return true;
}
void InProcessCommandBuffer::Destroy() {
CheckSequencedThread();
base::WaitableEvent completion(true, false);
bool result = false;
base::Callback<bool(void)> destroy_task = base::Bind(
&InProcessCommandBuffer::DestroyOnGpuThread, base::Unretained(this));
QueueTask(
base::Bind(&RunTaskWithResult<bool>, destroy_task, &result, &completion));
completion.Wait();
}
bool InProcessCommandBuffer::DestroyOnGpuThread() {
CheckSequencedThread();
gpu_thread_weak_ptr_factory_.InvalidateWeakPtrs();
command_buffer_.reset();
// Clean up GL resources if possible.
bool have_context = context_ && context_->MakeCurrent(surface_);
if (decoder_) {
decoder_->Destroy(have_context);
decoder_.reset();
}
context_ = NULL;
surface_ = NULL;
gl_share_group_ = NULL;
#if defined(OS_ANDROID)
stream_texture_manager_.reset();
#endif
return true;
}
void InProcessCommandBuffer::CheckSequencedThread() {
DCHECK(!sequence_checker_ ||
sequence_checker_->CalledOnValidSequencedThread());
}
void InProcessCommandBuffer::OnContextLost() {
CheckSequencedThread();
if (!context_lost_callback_.is_null()) {
context_lost_callback_.Run();
context_lost_callback_.Reset();
}
context_lost_ = true;
}
CommandBuffer::State InProcessCommandBuffer::GetStateFast() {
CheckSequencedThread();
base::AutoLock lock(state_after_last_flush_lock_);
if (state_after_last_flush_.generation - last_state_.generation < 0x80000000U)
last_state_ = state_after_last_flush_;
return last_state_;
}
CommandBuffer::State InProcessCommandBuffer::GetLastState() {
CheckSequencedThread();
return last_state_;
}
int32 InProcessCommandBuffer::GetLastToken() {
CheckSequencedThread();
GetStateFast();
return last_state_.token;
}
void InProcessCommandBuffer::FlushOnGpuThread(int32 put_offset) {
CheckSequencedThread();
ScopedEvent handle_flush(&flush_event_);
base::AutoLock lock(command_buffer_lock_);
command_buffer_->Flush(put_offset);
{
// Update state before signaling the flush event.
base::AutoLock lock(state_after_last_flush_lock_);
state_after_last_flush_ = command_buffer_->GetLastState();
}
DCHECK((!error::IsError(state_after_last_flush_.error) && !context_lost_) ||
(error::IsError(state_after_last_flush_.error) && context_lost_));
// If we've processed all pending commands but still have pending queries,
// pump idle work until the query is passed.
if (put_offset == state_after_last_flush_.get_offset &&
gpu_scheduler_->HasMoreWork()) {
service_->ScheduleIdleWork(
base::Bind(&InProcessCommandBuffer::ScheduleMoreIdleWork,
gpu_thread_weak_ptr_));
}
}
void InProcessCommandBuffer::ScheduleMoreIdleWork() {
CheckSequencedThread();
base::AutoLock lock(command_buffer_lock_);
if (gpu_scheduler_->HasMoreWork()) {
gpu_scheduler_->PerformIdleWork();
service_->ScheduleIdleWork(
base::Bind(&InProcessCommandBuffer::ScheduleMoreIdleWork,
gpu_thread_weak_ptr_));
}
}
void InProcessCommandBuffer::Flush(int32 put_offset) {
CheckSequencedThread();
if (last_state_.error != gpu::error::kNoError)
return;
if (last_put_offset_ == put_offset)
return;
last_put_offset_ = put_offset;
base::Closure task = base::Bind(&InProcessCommandBuffer::FlushOnGpuThread,
gpu_thread_weak_ptr_,
put_offset);
QueueTask(task);
}
void InProcessCommandBuffer::WaitForTokenInRange(int32 start, int32 end) {
CheckSequencedThread();
while (!InRange(start, end, GetLastToken()) &&
last_state_.error == gpu::error::kNoError)
flush_event_.Wait();
}
void InProcessCommandBuffer::WaitForGetOffsetInRange(int32 start, int32 end) {
CheckSequencedThread();
GetStateFast();
while (!InRange(start, end, last_state_.get_offset) &&
last_state_.error == gpu::error::kNoError) {
flush_event_.Wait();
GetStateFast();
}
}
void InProcessCommandBuffer::SetGetBuffer(int32 shm_id) {
CheckSequencedThread();
if (last_state_.error != gpu::error::kNoError)
return;
{
base::AutoLock lock(command_buffer_lock_);
command_buffer_->SetGetBuffer(shm_id);
last_put_offset_ = 0;
}
{
base::AutoLock lock(state_after_last_flush_lock_);
state_after_last_flush_ = command_buffer_->GetLastState();
}
}
scoped_refptr<Buffer> InProcessCommandBuffer::CreateTransferBuffer(size_t size,
int32* id) {
CheckSequencedThread();
base::AutoLock lock(command_buffer_lock_);
return command_buffer_->CreateTransferBuffer(size, id);
}
void InProcessCommandBuffer::DestroyTransferBuffer(int32 id) {
CheckSequencedThread();
base::Closure task =
base::Bind(&InProcessCommandBuffer::DestroyTransferBufferOnGputhread,
base::Unretained(this),
id);
QueueTask(task);
}
void InProcessCommandBuffer::DestroyTransferBufferOnGputhread(int32 id) {
base::AutoLock lock(command_buffer_lock_);
command_buffer_->DestroyTransferBuffer(id);
}
gpu::Capabilities InProcessCommandBuffer::GetCapabilities() {
return capabilities_;
}
gfx::GpuMemoryBuffer* InProcessCommandBuffer::CreateGpuMemoryBuffer(
size_t width,
size_t height,
unsigned internalformat,
unsigned usage,
int32* id) {
CheckSequencedThread();
*id = -1;
linked_ptr<gfx::GpuMemoryBuffer> buffer =
make_linked_ptr(g_gpu_memory_buffer_factory->CreateGpuMemoryBuffer(
width, height, internalformat, usage));
if (!buffer.get())
return NULL;
static int32 next_id = 1;
*id = next_id++;
base::Closure task = base::Bind(&GpuControlService::RegisterGpuMemoryBuffer,
base::Unretained(gpu_control_.get()),
*id,
buffer->GetHandle(),
width,
height,
internalformat);
QueueTask(task);
gpu_memory_buffers_[*id] = buffer;
return buffer.get();
}
void InProcessCommandBuffer::DestroyGpuMemoryBuffer(int32 id) {
CheckSequencedThread();
GpuMemoryBufferMap::iterator it = gpu_memory_buffers_.find(id);
if (it != gpu_memory_buffers_.end())
gpu_memory_buffers_.erase(it);
base::Closure task = base::Bind(&GpuControlService::UnregisterGpuMemoryBuffer,
base::Unretained(gpu_control_.get()),
id);
QueueTask(task);
}
uint32 InProcessCommandBuffer::InsertSyncPoint() {
uint32 sync_point = g_sync_point_manager.Get().GenerateSyncPoint();
QueueTask(base::Bind(&InProcessCommandBuffer::RetireSyncPointOnGpuThread,
base::Unretained(this),
sync_point));
return sync_point;
}
void InProcessCommandBuffer::RetireSyncPointOnGpuThread(uint32 sync_point) {
gles2::MailboxManager* mailbox_manager =
decoder_->GetContextGroup()->mailbox_manager();
if (mailbox_manager->UsesSync() && MakeCurrent())
mailbox_manager->PushTextureUpdates();
g_sync_point_manager.Get().RetireSyncPoint(sync_point);
}
void InProcessCommandBuffer::SignalSyncPoint(unsigned sync_point,
const base::Closure& callback) {
CheckSequencedThread();
QueueTask(base::Bind(&InProcessCommandBuffer::SignalSyncPointOnGpuThread,
base::Unretained(this),
sync_point,
WrapCallback(callback)));
}
void InProcessCommandBuffer::SignalSyncPointOnGpuThread(
unsigned sync_point,
const base::Closure& callback) {
if (g_sync_point_manager.Get().IsSyncPointPassed(sync_point)) {
callback.Run();
} else {
service_->ScheduleIdleWork(
base::Bind(&InProcessCommandBuffer::SignalSyncPointOnGpuThread,
gpu_thread_weak_ptr_,
sync_point,
callback));
}
}
void InProcessCommandBuffer::SignalQuery(unsigned query,
const base::Closure& callback) {
CheckSequencedThread();
QueueTask(base::Bind(&GpuControlService::SignalQuery,
base::Unretained(gpu_control_.get()),
query,
WrapCallback(callback)));
}
void InProcessCommandBuffer::SetSurfaceVisible(bool visible) {}
void InProcessCommandBuffer::Echo(const base::Closure& callback) {
QueueTask(WrapCallback(callback));
}
uint32 InProcessCommandBuffer::CreateStreamTexture(uint32 texture_id) {
base::WaitableEvent completion(true, false);
uint32 stream_id = 0;
base::Callback<uint32(void)> task =
base::Bind(&InProcessCommandBuffer::CreateStreamTextureOnGpuThread,
base::Unretained(this),
texture_id);
QueueTask(
base::Bind(&RunTaskWithResult<uint32>, task, &stream_id, &completion));
completion.Wait();
return stream_id;
}
uint32 InProcessCommandBuffer::CreateStreamTextureOnGpuThread(
uint32 client_texture_id) {
#if defined(OS_ANDROID)
return stream_texture_manager_->CreateStreamTexture(
client_texture_id, decoder_->GetContextGroup()->texture_manager());
#else
return 0;
#endif
}
gpu::error::Error InProcessCommandBuffer::GetLastError() {
CheckSequencedThread();
return last_state_.error;
}
bool InProcessCommandBuffer::Initialize() {
NOTREACHED();
return false;
}
namespace {
void PostCallback(const scoped_refptr<base::MessageLoopProxy>& loop,
const base::Closure& callback) {
if (!loop->BelongsToCurrentThread()) {
loop->PostTask(FROM_HERE, callback);
} else {
callback.Run();
}
}
void RunOnTargetThread(scoped_ptr<base::Closure> callback) {
DCHECK(callback.get());
callback->Run();
}
} // anonymous namespace
base::Closure InProcessCommandBuffer::WrapCallback(
const base::Closure& callback) {
// Make sure the callback gets deleted on the target thread by passing
// ownership.
scoped_ptr<base::Closure> scoped_callback(new base::Closure(callback));
base::Closure callback_on_client_thread =
base::Bind(&RunOnTargetThread, base::Passed(&scoped_callback));
base::Closure wrapped_callback =
base::Bind(&PostCallback, base::MessageLoopProxy::current(),
callback_on_client_thread);
return wrapped_callback;
}
#if defined(OS_ANDROID)
scoped_refptr<gfx::SurfaceTexture>
InProcessCommandBuffer::GetSurfaceTexture(uint32 stream_id) {
DCHECK(stream_texture_manager_);
return stream_texture_manager_->GetSurfaceTexture(stream_id);
}
#endif
// static
void InProcessCommandBuffer::SetGpuMemoryBufferFactory(
GpuMemoryBufferFactory* factory) {
g_gpu_memory_buffer_factory = factory;
}
} // namespace gpu