Files
android_development/tools/emulator/opengl/host/libs/libOpenglRender/WindowSurface.cpp
Guy Zadickario 8c962dce6c opengles emulator: replace the readback at end of frame with blit
That replaces the readback to host we do at the end of each frame
with two blits on the GPU, one to copy the pixels into a texture
and another to render the texture to another in order to flip
the image.

Change-Id: I7e0e10493d38944d0b613e245023f34236d3dfc4
2011-08-16 10:52:19 +03:00

368 lines
10 KiB
C++

/*
* 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 "WindowSurface.h"
#include "FBConfig.h"
#include "FrameBuffer.h"
#include <GLES/glext.h>
#include "EGLDispatch.h"
#include "GLDispatch.h"
#include "GL2Dispatch.h"
#include <stdio.h>
#include <string.h>
#include "GLErrorLog.h"
WindowSurface::WindowSurface() :
m_fbObj(0),
m_depthRB(0),
m_stencilRB(0),
m_eglSurface(NULL),
m_attachedColorBuffer(NULL),
m_readContext(NULL),
m_drawContext(NULL),
m_width(0),
m_height(0),
m_pbufWidth(0),
m_pbufHeight(0),
m_useEGLImage(false),
m_useBindToTexture(false)
{
}
WindowSurface::~WindowSurface()
{
s_egl.eglDestroySurface(FrameBuffer::getFB()->getDisplay(), m_eglSurface);
}
WindowSurface *WindowSurface::create(int p_config, int p_width, int p_height)
{
const FBConfig *fbconf = FBConfig::get(p_config);
if (!fbconf) {
return NULL;
}
// allocate space for the WindowSurface object
WindowSurface *win = new WindowSurface();
if (!win) {
return NULL;
}
win->m_fbconf = fbconf;
FrameBuffer *fb = FrameBuffer::getFB();
const FrameBufferCaps &caps = fb->getCaps();
//
// We can use eglimage and prevent copies if:
// GL_KHR_gl_texture_2D_image is present.
// and either there is no need for depth or stencil buffer
// or GL_KHR_gl_renderbuffer_image present.
//
#if 0
//XXX: This path should be implemented
win->m_useEGLImage =
(caps.has_eglimage_texture_2d &&
(caps.has_eglimage_renderbuffer ||
(fbconf->getDepthSize() + fbconf->getStencilSize() == 0)) );
#else
win->m_useEGLImage = false;
#endif
if (win->m_useEGLImage) {
}
else if (0 != (fbconf->getSurfaceType() & EGL_PBUFFER_BIT)) {
if (!win->resizePbuffer(p_width, p_height)) {
delete win;
return NULL;
}
}
else {
// no EGLImage support and not Pbuffer support - fail
delete win;
return NULL;
}
win->m_width = p_width;
win->m_height = p_height;
return win;
}
//
// flushColorBuffer - The function makes sure that the
// previous attached color buffer is updated, if copy or blit should be done
// in order to update it - it is being done here.
//
void WindowSurface::flushColorBuffer()
{
if (m_attachedColorBuffer.Ptr() != NULL) {
if (!m_useEGLImage) {
bool copied = false;
if (m_useBindToTexture) {
copied = m_attachedColorBuffer->blitFromPbuffer(m_eglSurface);
}
if (!copied) {
//copyToColorBuffer();
blitToColorBuffer();
}
}
else {
//TODO: EGLImage
}
}
}
//
// setColorBuffer - this function is called when a new color buffer needs to
// be attached to the surface. The function doesn't make sure that the
// previous attached color buffer is updated, this is done by flushColorBuffer
//
void WindowSurface::setColorBuffer(ColorBufferPtr p_colorBuffer)
{
m_attachedColorBuffer = p_colorBuffer;
//
// resize the window if the attached color buffer is of different
// size
//
unsigned int cbWidth = m_attachedColorBuffer->getWidth();
unsigned int cbHeight = m_attachedColorBuffer->getHeight();
if (cbWidth != m_width || cbHeight != m_height) {
if (m_pbufWidth && m_pbufHeight) {
// if we use pbuffer, need to resize it
resizePbuffer(cbWidth, cbHeight);
}
m_width = cbWidth;
m_height = cbHeight;
}
}
//
// This function is called after the context and eglSurface is already
// bound in the current thread (eglMakeCurrent has been called).
// This function should take actions required on the other surface objects
// when being bind/unbound
//
void WindowSurface::bind(RenderContextPtr p_ctx, SurfaceBindType p_bindType)
{
if (p_bindType == SURFACE_BIND_READ) {
m_readContext = p_ctx;
}
else if (p_bindType == SURFACE_BIND_DRAW) {
m_drawContext = p_ctx;
}
else if (p_bindType == SURFACE_BIND_READDRAW) {
m_readContext = p_ctx;
m_drawContext = p_ctx;
}
else {
return; // bad param
}
if (m_useEGLImage) {
// XXX: should be implemented
}
}
void WindowSurface::copyToColorBuffer()
{
if (!m_width && !m_height) return;
if (m_attachedColorBuffer->getWidth() != m_width ||
m_attachedColorBuffer->getHeight() != m_height) {
// XXX: should never happen - how this needs to be handled?
return;
}
void *data = m_xferBuffer.alloc(m_width * m_height * 4);
if (!data) {
fprintf(stderr,"WARNING: Failed to copy buffer data - OutOfMemory\n");
return;
}
//
// Make the surface current
//
EGLContext prevContext = s_egl.eglGetCurrentContext();
EGLSurface prevReadSurf = s_egl.eglGetCurrentSurface(EGL_READ);
EGLSurface prevDrawSurf = s_egl.eglGetCurrentSurface(EGL_DRAW);
FrameBuffer *fb = FrameBuffer::getFB();
if (!s_egl.eglMakeCurrent(fb->getDisplay(), m_eglSurface,
m_eglSurface, m_drawContext->getEGLContext())) {
return;
}
if (m_drawContext->isGL2()) {
#ifdef WITH_GLES2
s_gl2.glPixelStorei(GL_PACK_ALIGNMENT, 1);
s_gl2.glReadPixels(0, 0, m_width, m_height,
GL_RGBA, GL_UNSIGNED_BYTE, data);
#else
return; // should never happen, context cannot be GL2 in this case.
#endif
}
else {
s_gl.glPixelStorei(GL_PACK_ALIGNMENT, 1);
s_gl.glReadPixels(0, 0, m_width, m_height,
GL_RGBA, GL_UNSIGNED_BYTE, data);
}
//
// XXX: for some reason flipping the image is not required on
// Mac. Need to find the reason, currently unkbown.
//
#ifndef __APPLE__
#define FLIP_BUFFER 1
#endif
#if FLIP_BUFFER
//We need to flip the pixels
int bpp = 4;
void *tmpBuf = m_xUpdateBuf.alloc(m_width * m_height * bpp);
int dst_line_len = m_width * bpp;
int src_line_len = m_width * bpp;
char *src = (char *)data;
char *dst = (char*)tmpBuf + (m_height-1)*dst_line_len;
for (uint32_t y=0; y<m_height; y++) {
memcpy(dst, src, dst_line_len);
src += src_line_len;
dst -= dst_line_len;
}
// update the attached color buffer with the fliped readback pixels
m_attachedColorBuffer->update(GL_RGBA, GL_UNSIGNED_BYTE, tmpBuf);
#else
// update the attached color buffer with the readback pixels
m_attachedColorBuffer->update(GL_RGBA, GL_UNSIGNED_BYTE, data);
#endif
// restore current context/surface
s_egl.eglMakeCurrent(fb->getDisplay(), prevDrawSurf,
prevReadSurf, prevContext);
}
void WindowSurface::blitToColorBuffer()
{
if (!m_width && !m_height) return;
if (m_attachedColorBuffer->getWidth() != m_width ||
m_attachedColorBuffer->getHeight() != m_height) {
// XXX: should never happen - how this needs to be handled?
return;
}
//
// Make the surface current
//
EGLContext prevContext = s_egl.eglGetCurrentContext();
EGLSurface prevReadSurf = s_egl.eglGetCurrentSurface(EGL_READ);
EGLSurface prevDrawSurf = s_egl.eglGetCurrentSurface(EGL_DRAW);
FrameBuffer *fb = FrameBuffer::getFB();
if (!s_egl.eglMakeCurrent(fb->getDisplay(), m_eglSurface,
m_eglSurface, m_drawContext->getEGLContext())) {
return;
}
m_attachedColorBuffer->blitFromCurrentReadBuffer();
// restore current context/surface
s_egl.eglMakeCurrent(fb->getDisplay(), prevDrawSurf,
prevReadSurf, prevContext);
}
bool WindowSurface::resizePbuffer(unsigned int p_width, unsigned int p_height)
{
if (m_eglSurface &&
m_pbufWidth == p_width &&
m_pbufHeight == p_height) {
// no need to resize
return true;
}
FrameBuffer *fb = FrameBuffer::getFB();
EGLContext prevContext = s_egl.eglGetCurrentContext();
EGLSurface prevReadSurf = s_egl.eglGetCurrentSurface(EGL_READ);
EGLSurface prevDrawSurf = s_egl.eglGetCurrentSurface(EGL_DRAW);
EGLSurface prevPbuf = m_eglSurface;
bool needRebindContext = m_eglSurface &&
(prevReadSurf == m_eglSurface ||
prevDrawSurf == m_eglSurface);
if (needRebindContext) {
s_egl.eglMakeCurrent(fb->getDisplay(), EGL_NO_SURFACE,
EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
//
// Destroy previous surface
//
if (m_eglSurface) {
s_egl.eglDestroySurface(fb->getDisplay(), m_eglSurface);
m_eglSurface = NULL;
}
const FrameBufferCaps &caps = fb->getCaps();
//
// Create pbuffer surface, if possible
// set it such that it will be able to be bound to a texture
// later to prevent readback.
//
EGLint pbufAttribs[12];
pbufAttribs[0] = EGL_WIDTH;
pbufAttribs[1] = p_width;
pbufAttribs[2] = EGL_HEIGHT;
pbufAttribs[3] = p_height;
if (caps.has_BindToTexture) {
pbufAttribs[4] = EGL_TEXTURE_FORMAT;
pbufAttribs[5] = EGL_TEXTURE_RGBA;
pbufAttribs[6] = EGL_TEXTURE_TARGET;
pbufAttribs[7] = EGL_TEXTURE_2D;
pbufAttribs[8] = EGL_NONE;
m_useBindToTexture = true;
}
else {
pbufAttribs[4] = EGL_NONE;
}
m_eglSurface = s_egl.eglCreatePbufferSurface(fb->getDisplay(),
m_fbconf->getEGLConfig(),
pbufAttribs);
if (m_eglSurface == EGL_NO_SURFACE) {
fprintf(stderr, "Renderer error: failed to create/resize pbuffer!!\n");
return false;
}
m_pbufWidth = p_width;
m_pbufHeight = p_height;
if (needRebindContext) {
s_egl.eglMakeCurrent(fb->getDisplay(),
(prevDrawSurf==prevPbuf) ? m_eglSurface : prevDrawSurf,
(prevReadSurf==prevPbuf) ? m_eglSurface : prevReadSurf,
prevContext);
}
return true;
}