mirror of https://github.com/PCSX2/pcsx2.git
514 lines
15 KiB
C++
514 lines
15 KiB
C++
/*
|
|
* Copyright (C) 2011-2011 Gregory hainaut
|
|
* Copyright (C) 2007-2009 Gabest
|
|
*
|
|
* This Program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This Program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with GNU Make; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
*
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include "GSTextureOGL.h"
|
|
static uint g_state_texture_unit = 0;
|
|
static uint g_state_texture_id = 0;
|
|
|
|
GSTextureOGL::GSTextureOGL(int type, int w, int h, bool msaa, int format)
|
|
: m_texture_unit(0),
|
|
m_extra_buffer_id(0),
|
|
m_pbo_size(0)
|
|
{
|
|
// *************************************************************
|
|
// Opengl world
|
|
|
|
// http://www.opengl.org/wiki/Renderbuffer_Object
|
|
// render: constructor -> glGenRenderbuffers, glDeleteRenderbuffers
|
|
// info -> glGetRenderbufferParameteriv, glIsRenderbuffer
|
|
// binding -> glBindRenderbuffer (only 1 target), glFramebufferRenderbuffer (attach actually)
|
|
// setup param -> glRenderbufferStorageMultisample (after this call the buffer is unitialized)
|
|
//
|
|
// the only way to use a renderbuffer object is to attach it to a Framebuffer Object. After you bind that
|
|
// FBO to the context, and set up the draw or read buffers appropriately, you can use pixel transfer operations
|
|
// to read and write to it. Of course, you can also render to it. The standard glClear function will also clear the appropriate buffer.
|
|
//
|
|
// Render work in parallal with framebuffer object (FBO) http://www.opengl.org/wiki/Framebuffer_Objects
|
|
// render are attached to FBO through : glFramebufferRenderbuffer. You can query the number of colorattachement with GL_MAX_COLOR_ATTACHMENTS
|
|
// FBO : constructor -> glGenFramebuffers, glDeleteFramebuffers
|
|
// binding -> glBindFramebuffer (target can be read/write/both)
|
|
// blit -> glBlitFramebuffer (read FB to draw FB)
|
|
// info -> glIsFramebuffer, glGetFramebufferAttachmentParameter, glCheckFramebufferStatus
|
|
//
|
|
// There are two types of framebuffer-attachable images; texture images and renderbuffer images.
|
|
// If an image of a texture object is attached to a framebuffer, OpenGL performs "render to texture".
|
|
// And if an image of a renderbuffer object is attached to a framebuffer, then OpenGL performs "offscreen rendering".
|
|
// Blitting:
|
|
// glDrawBuffers
|
|
// glReadBuffer
|
|
// glBlitFramebuffer
|
|
|
|
// TODO: we can render directly into a texture so I'm not sure rendertarget are useful anymore !!!
|
|
// *************************************************************
|
|
m_size.x = w;
|
|
m_size.y = h;
|
|
m_format = format;
|
|
m_type = type;
|
|
m_msaa = msaa;
|
|
|
|
// == Then generate the texture or buffer.
|
|
// D3D11_BIND_RENDER_TARGET: Bind a texture as a render target for the output-merger stage.
|
|
// => glGenRenderbuffers with GL_COLOR_ATTACHMENTi (Hum glGenTextures might work too)
|
|
// D3D11_BIND_DEPTH_STENCIL: Bind a texture as a depth-stencil target for the output-merger stage.
|
|
// => glGenRenderbuffers with GL_DEPTH_STENCIL_ATTACHMENT
|
|
// D3D11_BIND_SHADER_RESOURCE: Bind a buffer or texture to a shader stage
|
|
// => glGenTextures
|
|
// D3D11_USAGE_STAGING: A resource that supports data transfer (copy) from the GPU to the CPU.
|
|
// glGenTextures
|
|
// glReadPixels seems to use a framebuffer. In this case you can setup the source
|
|
// with glReadBuffer(GL_COLOR_ATTACHMENTi)
|
|
// glGetTexImage: read pixels of a bound texture
|
|
// => To allow map/unmap. I think we can use a pixel buffer (target GL_PIXEL_UNPACK_BUFFER)
|
|
// http://www.opengl.org/wiki/Pixel_Buffer_Objects
|
|
|
|
// Generate the buffer
|
|
switch (m_type) {
|
|
case GSTexture::Offscreen:
|
|
//FIXME I not sure we need a pixel buffer object. It seems more a texture
|
|
// glGenBuffers(1, &m_texture_id);
|
|
// m_texture_target = GL_PIXEL_UNPACK_BUFFER;
|
|
// assert(0);
|
|
// Note there is also a buffer texture!!!
|
|
// http://www.opengl.org/wiki/Buffer_Texture
|
|
// Note: in this case it must use in GLSL
|
|
// gvec texelFetch(gsampler sampler, ivec texCoord, int lod[, int sample]);
|
|
// corollary we can maybe use it for multisample stuff
|
|
case GSTexture::Texture:
|
|
case GSTexture::RenderTarget:
|
|
glGenTextures(1, &m_texture_id);
|
|
m_texture_target = GL_TEXTURE_2D;
|
|
break;
|
|
case GSTexture::DepthStencil:
|
|
glGenRenderbuffers(1, &m_texture_id);
|
|
m_texture_target = GL_RENDERBUFFER;
|
|
break;
|
|
break;
|
|
case GSTexture::Backbuffer:
|
|
m_texture_target = 0;
|
|
m_texture_id = 0;
|
|
default:
|
|
break;
|
|
}
|
|
// Extra buffer to handle various pixel transfer
|
|
glGenBuffers(1, &m_extra_buffer_id);
|
|
|
|
uint msaa_level;
|
|
if (m_msaa) {
|
|
// FIXME which level of MSAA
|
|
msaa_level = 1;
|
|
} else {
|
|
msaa_level = 0;
|
|
}
|
|
|
|
// Allocate the buffer
|
|
switch (m_type) {
|
|
case GSTexture::DepthStencil:
|
|
glBindRenderbuffer(m_texture_target, m_texture_id);
|
|
glRenderbufferStorageMultisample(m_texture_target, msaa_level, m_format, m_size.y, m_size.x);
|
|
break;
|
|
case GSTexture::RenderTarget:
|
|
case GSTexture::Texture:
|
|
// FIXME
|
|
// Howto allocate the texture unit !!!
|
|
// In worst case the HW renderer seems to use 3 texture unit
|
|
// For the moment SW renderer only use 1 so don't bother
|
|
EnableUnit(0);
|
|
if (m_format == GL_RGBA8) {
|
|
glTexImage2D(m_texture_target, 0, m_format, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
|
}
|
|
else
|
|
assert(0); // TODO Later
|
|
break;
|
|
case GSTexture::Offscreen:
|
|
if (m_type == GL_RGBA8) m_pbo_size = m_size.x * m_size.y * 4;
|
|
else if (m_type == GL_R16UI) m_pbo_size = m_size.x * m_size.y * 2;
|
|
else assert(0);
|
|
|
|
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_extra_buffer_id);
|
|
glBufferData(GL_PIXEL_PACK_BUFFER, m_pbo_size, NULL, GL_STREAM_DRAW);
|
|
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
|
break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
GSTextureOGL::~GSTextureOGL()
|
|
{
|
|
glDeleteBuffers(1, &m_extra_buffer_id);
|
|
switch (m_type) {
|
|
case GSTexture::Texture:
|
|
case GSTexture::RenderTarget:
|
|
glDeleteTextures(1, &m_texture_id);
|
|
break;
|
|
case GSTexture::DepthStencil:
|
|
glDeleteRenderbuffers(1, &m_texture_id);
|
|
break;
|
|
case GSTexture::Offscreen:
|
|
assert(0);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void GSTextureOGL::Attach(GLenum attachment)
|
|
{
|
|
if (m_type == GSTexture::DepthStencil)
|
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, m_texture_target, m_texture_id);
|
|
else
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, m_texture_target, m_texture_id, 0);
|
|
}
|
|
|
|
bool GSTextureOGL::Update(const GSVector4i& r, const void* data, int pitch)
|
|
{
|
|
// static int update_count = 0;
|
|
// update_count++;
|
|
// To update only a part of the texture you can use:
|
|
// glTexSubImage2D — specify a two-dimensional texture subimage
|
|
if (m_type == GSTexture::DepthStencil || m_type == GSTexture::Offscreen) assert(0);
|
|
|
|
// glTexSubImage2D specifies a two-dimensional subtexture for the current texture unit, specified with glActiveTexture.
|
|
// If a non-zero named buffer object is bound to the GL_PIXEL_UNPACK_BUFFER target
|
|
//(see glBindBuffer) while a texture image is
|
|
// specified, data is treated as a byte offset into the buffer object's data store
|
|
// FIXME warning order of the y axis
|
|
// FIXME I'm not confident with GL_UNSIGNED_BYTE type
|
|
// FIXME add a state check
|
|
|
|
EnableUnit(0);
|
|
|
|
if (m_format != GL_RGBA8) assert(0);
|
|
|
|
// FIXME. That suck but I don't know how to do better for now. I think we need to create a texture buffer and directly upload the copy
|
|
// The case appears on SW mode. Src pitch is 2x dst pitch.
|
|
int rowbytes = r.width() << 2;
|
|
if (pitch != rowbytes) {
|
|
uint32 map_flags = GL_MAP_WRITE_BIT;
|
|
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_extra_buffer_id);
|
|
if (!m_pbo_size) {
|
|
m_pbo_size = m_size.x * m_size.y * 4;
|
|
glBufferData(GL_PIXEL_UNPACK_BUFFER, m_pbo_size, NULL, GL_STREAM_DRAW);
|
|
} else {
|
|
GL_MAP_INVALIDATE_BUFFER_BIT;
|
|
}
|
|
|
|
uint8* src = (uint8*) data;
|
|
uint8* dst = (uint8*) glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, m_pbo_size, map_flags);
|
|
for(int h = r.height(); h > 0; h--, src += pitch, dst += rowbytes)
|
|
{
|
|
memcpy(dst, src, rowbytes);
|
|
}
|
|
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
|
|
|
glTexSubImage2D(m_texture_target, 0, r.x, r.y, r.width(), r.height(), GL_RGBA, GL_UNSIGNED_BYTE, 0 /* offset in UNPACK BUFFER */);
|
|
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
|
|
} else {
|
|
glTexSubImage2D(m_texture_target, 0, r.x, r.y, r.width(), r.height(), GL_RGBA, GL_UNSIGNED_BYTE, data);
|
|
}
|
|
|
|
#if 0
|
|
if(m_dev && m_texture)
|
|
{
|
|
D3D11_BOX box = {r.left, r.top, 0, r.right, r.bottom, 1};
|
|
|
|
m_ctx->UpdateSubresource(m_texture, 0, &box, data, pitch, 0);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void GSTextureOGL::EnableUnit(uint unit)
|
|
{
|
|
switch (m_type) {
|
|
case GSTexture::DepthStencil:
|
|
case GSTexture::Offscreen:
|
|
assert(0);
|
|
break;
|
|
case GSTexture::RenderTarget:
|
|
case GSTexture::Texture:
|
|
// FIXME
|
|
// Howto allocate the texture unit !!!
|
|
// In worst case the HW renderer seems to use 3 texture unit
|
|
// For the moment SW renderer only use 1 so don't bother
|
|
if (g_state_texture_unit != unit) {
|
|
g_state_texture_unit = unit;
|
|
glActiveTexture(GL_TEXTURE0 + unit);
|
|
// When you change the texture unit, texture must be rebinded
|
|
g_state_texture_id = m_texture_id;
|
|
glBindTexture(m_texture_target, m_texture_id);
|
|
} else if (g_state_texture_id != m_texture_id) {
|
|
g_state_texture_id = m_texture_id;
|
|
glBindTexture(m_texture_target, m_texture_id);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool GSTextureOGL::Map(GSMap& m, const GSVector4i* r)
|
|
{
|
|
// The function allow to modify the texture from the CPU
|
|
// Set m.bits <- pointer to the data
|
|
// Set m.pitch <- size of a row
|
|
// I think in opengl we need to copy back the data to the RAM: glReadPixels — read a block of pixels from the frame buffer
|
|
//
|
|
// glMapBuffer — map a buffer object's data store
|
|
// Can be used on GL_PIXEL_UNPACK_BUFFER or GL_TEXTURE_BUFFER
|
|
if (m_type == GSTexture::Offscreen) {
|
|
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_extra_buffer_id);
|
|
// FIXME It might be possible to only map a subrange of the texture based on r object
|
|
|
|
// Load the PBO
|
|
if (m_format == GL_R16UI)
|
|
glReadPixels(0, 0, m_size.x, m_size.y, GL_RED, GL_UNSIGNED_SHORT, 0);
|
|
else if (m_format == GL_RGBA8)
|
|
glReadPixels(0, 0, m_size.x, m_size.y, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
|
else
|
|
assert(0);
|
|
|
|
// Give access from the CPU
|
|
uint32 map_flags = GL_MAP_READ_BIT;
|
|
m.bits = (uint8*) glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, m_pbo_size, map_flags);
|
|
m.pitch = m_size.x;
|
|
|
|
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
#if 0
|
|
if(r != NULL)
|
|
{
|
|
// ASSERT(0); // not implemented
|
|
|
|
return false;
|
|
}
|
|
|
|
if(m_texture && m_desc.Usage == D3D11_USAGE_STAGING)
|
|
{
|
|
D3D11_MAPPED_SUBRESOURCE map;
|
|
|
|
if(SUCCEEDED(m_ctx->Map(m_texture, 0, D3D11_MAP_READ_WRITE, 0, &map)))
|
|
{
|
|
m.bits = (uint8*)map.pData;
|
|
m.pitch = (int)map.RowPitch;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void GSTextureOGL::Unmap()
|
|
{
|
|
if (m_type == GSTexture::Offscreen) {
|
|
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
|
|
}
|
|
}
|
|
|
|
#ifndef _WINDOWS
|
|
|
|
#pragma pack(push, 1)
|
|
|
|
struct BITMAPFILEHEADER
|
|
{
|
|
uint16 bfType;
|
|
uint32 bfSize;
|
|
uint16 bfReserved1;
|
|
uint16 bfReserved2;
|
|
uint32 bfOffBits;
|
|
};
|
|
|
|
struct BITMAPINFOHEADER
|
|
{
|
|
uint32 biSize;
|
|
int32 biWidth;
|
|
int32 biHeight;
|
|
uint16 biPlanes;
|
|
uint16 biBitCount;
|
|
uint32 biCompression;
|
|
uint32 biSizeImage;
|
|
int32 biXPelsPerMeter;
|
|
int32 biYPelsPerMeter;
|
|
uint32 biClrUsed;
|
|
uint32 biClrImportant;
|
|
};
|
|
|
|
#define BI_RGB 0
|
|
|
|
#pragma pack(pop)
|
|
|
|
#endif
|
|
|
|
bool GSTextureOGL::Save(const string& fn, bool dds)
|
|
{
|
|
// Code not yet working
|
|
if (IsDss()) return false;
|
|
|
|
// Collect the texture data
|
|
uint32 pitch = 4 * m_size.x;
|
|
if (IsDss()) pitch *= 2;
|
|
char* image = (char*)malloc(pitch * m_size.y);
|
|
|
|
if (IsBackbuffer()) {
|
|
// TODO backbuffer
|
|
glReadBuffer(GL_BACK);
|
|
glReadPixels(0, 0, m_size.x, m_size.y, GL_RGBA, GL_UNSIGNED_BYTE, image);
|
|
} else if(IsDss()) {
|
|
Attach(GL_DEPTH_STENCIL_ATTACHMENT);
|
|
glGetTexImage(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV, image);
|
|
} else {
|
|
EnableUnit(0);
|
|
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, image);
|
|
}
|
|
|
|
// Build a BMP file
|
|
if(FILE* fp = fopen(fn.c_str(), "wb"))
|
|
{
|
|
BITMAPINFOHEADER bih;
|
|
|
|
memset(&bih, 0, sizeof(bih));
|
|
|
|
bih.biSize = sizeof(bih);
|
|
bih.biWidth = m_size.x;
|
|
bih.biHeight = m_size.y;
|
|
bih.biPlanes = 1;
|
|
bih.biBitCount = 32;
|
|
bih.biCompression = BI_RGB;
|
|
bih.biSizeImage = m_size.x * m_size.y << 2;
|
|
|
|
BITMAPFILEHEADER bfh;
|
|
|
|
memset(&bfh, 0, sizeof(bfh));
|
|
|
|
uint8* bfType = (uint8*)&bfh.bfType;
|
|
|
|
// bfh.bfType = 'MB';
|
|
bfType[0] = 0x42;
|
|
bfType[1] = 0x4d;
|
|
bfh.bfOffBits = sizeof(bfh) + sizeof(bih);
|
|
bfh.bfSize = bfh.bfOffBits + bih.biSizeImage;
|
|
bfh.bfReserved1 = bfh.bfReserved2 = 0;
|
|
|
|
fwrite(&bfh, 1, sizeof(bfh), fp);
|
|
fwrite(&bih, 1, sizeof(bih), fp);
|
|
|
|
uint8* data = (uint8*)image + (m_size.y - 1) * pitch;
|
|
|
|
for(int h = m_size.y; h > 0; h--, data -= pitch)
|
|
{
|
|
if (IsDss()) {
|
|
// Only get the depth and convert it to an integer
|
|
uint8* better_data = data;
|
|
for (int w = m_size.x; w > 0; w--, better_data += 8) {
|
|
float* input = (float*)better_data;
|
|
uint32 depth = (uint32)ldexpf(*input, 32);
|
|
fwrite(&depth, 1, 4, fp);
|
|
}
|
|
} else {
|
|
// swap red and blue
|
|
uint8* better_data = data;
|
|
for (int w = m_size.x; w > 0; w--, better_data += 4) {
|
|
uint8 red = better_data[2];
|
|
better_data[2] = better_data[0];
|
|
better_data[0] = red;
|
|
fwrite(better_data, 1, 4, fp);
|
|
}
|
|
}
|
|
// fwrite(data, 1, m_size.x << 2, fp); // TODO: swap red-blue?
|
|
}
|
|
|
|
fclose(fp);
|
|
|
|
free(image);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
#if 0
|
|
CComPtr<ID3D11Resource> res;
|
|
|
|
if(m_desc.BindFlags & D3D11_BIND_DEPTH_STENCIL)
|
|
{
|
|
HRESULT hr;
|
|
|
|
D3D11_TEXTURE2D_DESC desc;
|
|
|
|
memset(&desc, 0, sizeof(desc));
|
|
|
|
m_texture->GetDesc(&desc);
|
|
|
|
desc.Usage = D3D11_USAGE_STAGING;
|
|
desc.BindFlags = 0;
|
|
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
|
|
|
CComPtr<ID3D11Texture2D> src, dst;
|
|
|
|
hr = m_dev->CreateTexture2D(&desc, NULL, &src);
|
|
|
|
m_ctx->CopyResource(src, m_texture);
|
|
|
|
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
|
|
|
|
hr = m_dev->CreateTexture2D(&desc, NULL, &dst);
|
|
|
|
D3D11_MAPPED_SUBRESOURCE sm, dm;
|
|
|
|
hr = m_ctx->Map(src, 0, D3D11_MAP_READ, 0, &sm);
|
|
hr = m_ctx->Map(dst, 0, D3D11_MAP_WRITE, 0, &dm);
|
|
|
|
uint8* s = (uint8*)sm.pData;
|
|
uint8* d = (uint8*)dm.pData;
|
|
|
|
for(uint32 y = 0; y < desc.Height; y++, s += sm.RowPitch, d += dm.RowPitch)
|
|
{
|
|
for(uint32 x = 0; x < desc.Width; x++)
|
|
{
|
|
((uint32*)d)[x] = (uint32)(ldexpf(((float*)s)[x*2], 32));
|
|
}
|
|
}
|
|
|
|
m_ctx->Unmap(src, 0);
|
|
m_ctx->Unmap(dst, 0);
|
|
|
|
res = dst;
|
|
}
|
|
else
|
|
{
|
|
res = m_texture;
|
|
}
|
|
|
|
return SUCCEEDED(D3DX11SaveTextureToFile(m_ctx, res, dds ? D3DX11_IFF_DDS : D3DX11_IFF_BMP, fn.c_str()));
|
|
#endif
|
|
}
|
|
|