xemu/hw/xbox/nv2a/pgraph/gl/surface.c

1429 lines
50 KiB
C

/*
* Geforce NV2A PGRAPH OpenGL Renderer
*
* Copyright (c) 2012 espes
* Copyright (c) 2015 Jannik Vogel
* Copyright (c) 2018-2024 Matt Borgerson
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include "hw/xbox/nv2a/pgraph/pgraph.h"
#include "ui/xemu-settings.h"
#include "hw/xbox/nv2a/nv2a_int.h"
#include "hw/xbox/nv2a/pgraph/swizzle.h"
#include "debug.h"
#include "renderer.h"
static void surface_download(NV2AState *d, SurfaceBinding *surface, bool force);
static void surface_download_to_buffer(NV2AState *d, SurfaceBinding *surface,
bool swizzle, bool flip, bool downscale,
uint8_t *pixels);
static void surface_get_dimensions(PGRAPHState *pg, unsigned int *width, unsigned int *height);
void pgraph_gl_set_surface_scale_factor(NV2AState *d, unsigned int scale)
{
PGRAPHState *pg = &d->pgraph;
PGRAPHGLState *r = pg->gl_renderer_state;
g_config.display.quality.surface_scale = scale < 1 ? 1 : scale;
qemu_mutex_lock(&d->pfifo.lock);
qatomic_set(&d->pfifo.halt, true);
qemu_mutex_unlock(&d->pfifo.lock);
qemu_mutex_lock(&d->pgraph.lock);
qemu_event_reset(&r->dirty_surfaces_download_complete);
qatomic_set(&r->download_dirty_surfaces_pending, true);
qemu_mutex_unlock(&d->pgraph.lock);
qemu_mutex_lock(&d->pfifo.lock);
pfifo_kick(d);
qemu_mutex_unlock(&d->pfifo.lock);
qemu_event_wait(&r->dirty_surfaces_download_complete);
qemu_mutex_lock(&d->pgraph.lock);
qemu_event_reset(&d->pgraph.flush_complete);
qatomic_set(&d->pgraph.flush_pending, true);
qemu_mutex_unlock(&d->pgraph.lock);
qemu_mutex_lock(&d->pfifo.lock);
pfifo_kick(d);
qemu_mutex_unlock(&d->pfifo.lock);
qemu_event_wait(&d->pgraph.flush_complete);
qemu_mutex_lock(&d->pfifo.lock);
qatomic_set(&d->pfifo.halt, false);
pfifo_kick(d);
qemu_mutex_unlock(&d->pfifo.lock);
}
unsigned int pgraph_gl_get_surface_scale_factor(NV2AState *d)
{
return d->pgraph.surface_scale_factor;
}
void pgraph_gl_reload_surface_scale_factor(PGRAPHState *pg)
{
int factor = g_config.display.quality.surface_scale;
pg->surface_scale_factor = factor < 1 ? 1 : factor;
}
// FIXME: Move to common
static bool framebuffer_dirty(PGRAPHState *pg)
{
bool shape_changed = memcmp(&pg->surface_shape, &pg->last_surface_shape,
sizeof(SurfaceShape)) != 0;
if (!shape_changed || (!pg->surface_shape.color_format
&& !pg->surface_shape.zeta_format)) {
return false;
}
return true;
}
void pgraph_gl_set_surface_dirty(PGRAPHState *pg, bool color, bool zeta)
{
PGRAPHGLState *r = pg->gl_renderer_state;
NV2A_DPRINTF("pgraph_set_surface_dirty(%d, %d) -- %d %d\n",
color, zeta,
pgraph_color_write_enabled(pg), pgraph_zeta_write_enabled(pg));
/* FIXME: Does this apply to CLEARs too? */
color = color && pgraph_color_write_enabled(pg);
zeta = zeta && pgraph_zeta_write_enabled(pg);
pg->surface_color.draw_dirty |= color;
pg->surface_zeta.draw_dirty |= zeta;
if (r->color_binding) {
r->color_binding->draw_dirty |= color;
r->color_binding->frame_time = pg->frame_time;
r->color_binding->cleared = false;
}
if (r->zeta_binding) {
r->zeta_binding->draw_dirty |= zeta;
r->zeta_binding->frame_time = pg->frame_time;
r->zeta_binding->cleared = false;
}
}
static void init_render_to_texture(PGRAPHState *pg)
{
PGRAPHGLState *r = pg->gl_renderer_state;
const char *vs =
"#version 330\n"
"void main()\n"
"{\n"
" float x = -1.0 + float((gl_VertexID & 1) << 2);\n"
" float y = -1.0 + float((gl_VertexID & 2) << 1);\n"
" gl_Position = vec4(x, y, 0, 1);\n"
"}\n";
const char *fs =
"#version 330\n"
"uniform sampler2D tex;\n"
"uniform vec2 surface_size;\n"
"layout(location = 0) out vec4 out_Color;\n"
"void main()\n"
"{\n"
" vec2 texCoord;\n"
" texCoord.x = gl_FragCoord.x;\n"
" texCoord.y = (surface_size.y - gl_FragCoord.y)\n"
" + (textureSize(tex,0).y - surface_size.y);\n"
" texCoord /= textureSize(tex,0).xy;\n"
" out_Color.rgba = texture(tex, texCoord);\n"
"}\n";
r->s2t_rndr.prog = pgraph_gl_compile_shader(vs, fs);
r->s2t_rndr.tex_loc = glGetUniformLocation(r->s2t_rndr.prog, "tex");
r->s2t_rndr.surface_size_loc = glGetUniformLocation(r->s2t_rndr.prog,
"surface_size");
glGenVertexArrays(1, &r->s2t_rndr.vao);
glBindVertexArray(r->s2t_rndr.vao);
glGenBuffers(1, &r->s2t_rndr.vbo);
glBindBuffer(GL_ARRAY_BUFFER, r->s2t_rndr.vbo);
glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STATIC_DRAW);
glGenFramebuffers(1, &r->s2t_rndr.fbo);
}
static void finalize_render_to_texture(PGRAPHState *pg)
{
PGRAPHGLState *r = pg->gl_renderer_state;
glDeleteProgram(r->s2t_rndr.prog);
r->s2t_rndr.prog = 0;
glDeleteVertexArrays(1, &r->s2t_rndr.vao);
r->s2t_rndr.vao = 0;
glDeleteBuffers(1, &r->s2t_rndr.vbo);
r->s2t_rndr.vbo = 0;
glDeleteFramebuffers(1, &r->s2t_rndr.fbo);
r->s2t_rndr.fbo = 0;
}
static bool surface_to_texture_can_fastpath(SurfaceBinding *surface,
TextureShape *shape)
{
// FIXME: Better checks/handling on formats and surface-texture compat
int surface_fmt = surface->shape.color_format;
int texture_fmt = shape->color_format;
if (!surface->color) {
// FIXME: Support zeta to color
return false;
}
switch (surface_fmt) {
case NV097_SET_SURFACE_FORMAT_COLOR_LE_X1R5G5B5_Z1R5G5B5: switch (texture_fmt) {
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X1R5G5B5: return true;
default: break;
}
break;
case NV097_SET_SURFACE_FORMAT_COLOR_LE_R5G6B5: switch (texture_fmt) {
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_R5G6B5: return true;
case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R5G6B5: return true;
default: break;
}
break;
case NV097_SET_SURFACE_FORMAT_COLOR_LE_X8R8G8B8_Z8R8G8B8: switch(texture_fmt) {
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X8R8G8B8: return true;
case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X8R8G8B8: return true;
default: break;
}
break;
case NV097_SET_SURFACE_FORMAT_COLOR_LE_A8R8G8B8: switch (texture_fmt) {
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8B8G8R8: return true;
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_R8G8B8A8: return true;
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8R8G8B8: return true;
case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8R8G8B8: return true;
default: break;
}
break;
default: break;
}
trace_nv2a_pgraph_surface_texture_compat_failed(
surface_fmt, texture_fmt);
return false;
}
static void render_surface_to(NV2AState *d, SurfaceBinding *surface,
int texture_unit, GLuint gl_target,
GLuint gl_texture, unsigned int width,
unsigned int height)
{
PGRAPHState *pg = &d->pgraph;
PGRAPHGLState *r = pg->gl_renderer_state;
glActiveTexture(GL_TEXTURE0 + texture_unit);
glBindFramebuffer(GL_FRAMEBUFFER, r->s2t_rndr.fbo);
GLenum draw_buffers[1] = { GL_COLOR_ATTACHMENT0 };
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, gl_target,
gl_texture, 0);
glDrawBuffers(1, draw_buffers);
assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
assert(glGetError() == GL_NO_ERROR);
float color[] = { 0.0f, 0.0f, 0.0f, 0.0f };
glBindTexture(GL_TEXTURE_2D, surface->gl_buffer);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color);
glBindVertexArray(r->s2t_rndr.vao);
glBindBuffer(GL_ARRAY_BUFFER, r->s2t_rndr.vbo);
glUseProgram(r->s2t_rndr.prog);
glProgramUniform1i(r->s2t_rndr.prog, r->s2t_rndr.tex_loc,
texture_unit);
glProgramUniform2f(r->s2t_rndr.prog,
r->s2t_rndr.surface_size_loc, width, height);
glViewport(0, 0, width, height);
glColorMask(true, true, true, true);
glDisable(GL_DITHER);
glDisable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
glDisable(GL_STENCIL_TEST);
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, 3);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, gl_target, 0,
0);
glBindFramebuffer(GL_FRAMEBUFFER, r->gl_framebuffer);
glBindVertexArray(r->gl_vertex_array);
glBindTexture(gl_target, gl_texture);
glUseProgram(
r->shader_binding ? r->shader_binding->gl_program : 0);
}
static void render_surface_to_texture_slow(NV2AState *d,
SurfaceBinding *surface,
TextureBinding *texture,
TextureShape *texture_shape,
int texture_unit)
{
PGRAPHState *pg = &d->pgraph;
const ColorFormatInfo *f = &kelvin_color_format_gl_map[texture_shape->color_format];
assert(texture_shape->color_format < ARRAY_SIZE(kelvin_color_format_gl_map));
nv2a_profile_inc_counter(NV2A_PROF_SURF_TO_TEX_FALLBACK);
glActiveTexture(GL_TEXTURE0 + texture_unit);
glBindTexture(texture->gl_target, texture->gl_texture);
unsigned int width = surface->width,
height = surface->height;
pgraph_apply_scaling_factor(pg, &width, &height);
size_t bufsize = width * height * surface->fmt.bytes_per_pixel;
uint8_t *buf = g_malloc(bufsize);
surface_download_to_buffer(d, surface, false, true, false, buf);
width = texture_shape->width;
height = texture_shape->height;
pgraph_apply_scaling_factor(pg, &width, &height);
glTexImage2D(texture->gl_target, 0, f->gl_internal_format, width, height, 0,
f->gl_format, f->gl_type, buf);
g_free(buf);
glBindTexture(texture->gl_target, texture->gl_texture);
}
/* Note: This function is intended to be called before PGRAPH configures GL
* state for rendering; it will configure GL state here but only restore a
* couple of items.
*/
void pgraph_gl_render_surface_to_texture(NV2AState *d, SurfaceBinding *surface,
TextureBinding *texture,
TextureShape *texture_shape,
int texture_unit)
{
PGRAPHState *pg = &d->pgraph;
PGRAPHGLState *r = pg->gl_renderer_state;
const ColorFormatInfo *f =
&kelvin_color_format_gl_map[texture_shape->color_format];
assert(texture_shape->color_format < ARRAY_SIZE(kelvin_color_format_gl_map));
nv2a_profile_inc_counter(NV2A_PROF_SURF_TO_TEX);
if (!surface_to_texture_can_fastpath(surface, texture_shape)) {
render_surface_to_texture_slow(d, surface, texture,
texture_shape, texture_unit);
return;
}
unsigned int width = texture_shape->width, height = texture_shape->height;
pgraph_apply_scaling_factor(pg, &width, &height);
glActiveTexture(GL_TEXTURE0 + texture_unit);
glBindTexture(texture->gl_target, texture->gl_texture);
glTexParameteri(texture->gl_target, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(texture->gl_target, GL_TEXTURE_MAX_LEVEL, 0);
glTexParameteri(texture->gl_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(texture->gl_target, 0, f->gl_internal_format, width, height, 0,
f->gl_format, f->gl_type, NULL);
glBindTexture(texture->gl_target, 0);
render_surface_to(d, surface, texture_unit, texture->gl_target,
texture->gl_texture, width, height);
glBindTexture(texture->gl_target, texture->gl_texture);
glUseProgram(
r->shader_binding ? r->shader_binding->gl_program : 0);
}
bool pgraph_gl_check_surface_to_texture_compatibility(
const SurfaceBinding *surface,
const TextureShape *shape)
{
// FIXME: Better checks/handling on formats and surface-texture compat
if ((!surface->swizzle && surface->pitch != shape->pitch) ||
surface->width != shape->width ||
surface->height != shape->height) {
return false;
}
int surface_fmt = surface->shape.color_format;
int texture_fmt = shape->color_format;
if (!surface->color) {
// FIXME: Support zeta to color
return false;
}
if (shape->cubemap) {
// FIXME: Support rendering surface to cubemap face
return false;
}
if (shape->levels > 1) {
// FIXME: Support rendering surface to mip levels
return false;
}
switch (surface_fmt) {
case NV097_SET_SURFACE_FORMAT_COLOR_LE_X1R5G5B5_Z1R5G5B5: switch (texture_fmt) {
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X1R5G5B5: return true;
default: break;
}
break;
case NV097_SET_SURFACE_FORMAT_COLOR_LE_R5G6B5: switch (texture_fmt) {
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_R5G6B5: return true;
case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R5G6B5: return true;
default: break;
}
break;
case NV097_SET_SURFACE_FORMAT_COLOR_LE_X8R8G8B8_Z8R8G8B8: switch(texture_fmt) {
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X8R8G8B8: return true;
case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X8R8G8B8: return true;
default: break;
}
break;
case NV097_SET_SURFACE_FORMAT_COLOR_LE_A8R8G8B8: switch (texture_fmt) {
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8B8G8R8: return true;
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_R8G8B8A8: return true;
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8R8G8B8: return true;
case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8R8G8B8: return true;
default: break;
}
break;
default:
break;
}
trace_nv2a_pgraph_surface_texture_compat_failed(
surface_fmt, texture_fmt);
return false;
}
static void wait_for_surface_download(SurfaceBinding *e)
{
NV2AState *d = g_nv2a;
PGRAPHState *pg = &d->pgraph;
PGRAPHGLState *r = pg->gl_renderer_state;
if (qatomic_read(&e->draw_dirty)) {
qemu_mutex_lock(&d->pfifo.lock);
qemu_event_reset(&r->downloads_complete);
qatomic_set(&e->download_pending, true);
qatomic_set(&r->downloads_pending, true);
pfifo_kick(d);
qemu_mutex_unlock(&d->pfifo.lock);
qemu_event_wait(&r->downloads_complete);
}
}
static void surface_access_callback(void *opaque, MemoryRegion *mr, hwaddr addr,
hwaddr len, bool write)
{
SurfaceBinding *e = opaque;
assert(addr >= e->vram_addr);
hwaddr offset = addr - e->vram_addr;
assert(offset < e->size);
if (qatomic_read(&e->draw_dirty)) {
trace_nv2a_pgraph_surface_cpu_access(e->vram_addr, offset);
wait_for_surface_download(e);
}
if (write && !qatomic_read(&e->upload_pending)) {
trace_nv2a_pgraph_surface_cpu_access(e->vram_addr, offset);
qatomic_set(&e->upload_pending, true);
}
}
static SurfaceBinding *surface_put(NV2AState *d, hwaddr addr,
SurfaceBinding *surface_in)
{
PGRAPHState *pg = &d->pgraph;
PGRAPHGLState *r = pg->gl_renderer_state;
assert(pgraph_gl_surface_get(d, addr) == NULL);
SurfaceBinding *surface, *next;
uintptr_t e_end = surface_in->vram_addr + surface_in->size - 1;
QTAILQ_FOREACH_SAFE(surface, &r->surfaces, entry, next) {
uintptr_t s_end = surface->vram_addr + surface->size - 1;
bool overlapping = !(surface->vram_addr > e_end
|| surface_in->vram_addr > s_end);
if (overlapping) {
trace_nv2a_pgraph_surface_evict_overlapping(
surface->vram_addr, surface->width, surface->height,
surface->pitch);
pgraph_gl_surface_download_if_dirty(d, surface);
pgraph_gl_surface_invalidate(d, surface);
}
}
SurfaceBinding *surface_out = g_malloc(sizeof(SurfaceBinding));
assert(surface_out != NULL);
*surface_out = *surface_in;
if (tcg_enabled()) {
qemu_mutex_unlock(&d->pgraph.lock);
qemu_mutex_lock_iothread();
mem_access_callback_insert(qemu_get_cpu(0),
d->vram, surface_out->vram_addr, surface_out->size,
&surface_out->access_cb, &surface_access_callback,
surface_out);
qemu_mutex_unlock_iothread();
qemu_mutex_lock(&d->pgraph.lock);
}
QTAILQ_INSERT_TAIL(&r->surfaces, surface_out, entry);
return surface_out;
}
SurfaceBinding *pgraph_gl_surface_get(NV2AState *d, hwaddr addr)
{
PGRAPHState *pg = &d->pgraph;
PGRAPHGLState *r = pg->gl_renderer_state;
SurfaceBinding *surface;
QTAILQ_FOREACH (surface, &r->surfaces, entry) {
if (surface->vram_addr == addr) {
return surface;
}
}
return NULL;
}
SurfaceBinding *pgraph_gl_surface_get_within(NV2AState *d, hwaddr addr)
{
PGRAPHState *pg = &d->pgraph;
PGRAPHGLState *r = pg->gl_renderer_state;
SurfaceBinding *surface;
QTAILQ_FOREACH (surface, &r->surfaces, entry) {
if (addr >= surface->vram_addr &&
addr < (surface->vram_addr + surface->size)) {
return surface;
}
}
return NULL;
}
void pgraph_gl_surface_invalidate(NV2AState *d, SurfaceBinding *surface)
{
PGRAPHState *pg = &d->pgraph;
PGRAPHGLState *r = pg->gl_renderer_state;
trace_nv2a_pgraph_surface_invalidated(surface->vram_addr);
if (surface == r->color_binding) {
assert(d->pgraph.surface_color.buffer_dirty);
pgraph_gl_unbind_surface(d, true);
}
if (surface == r->zeta_binding) {
assert(d->pgraph.surface_zeta.buffer_dirty);
pgraph_gl_unbind_surface(d, false);
}
if (tcg_enabled()) {
qemu_mutex_unlock(&d->pgraph.lock);
qemu_mutex_lock_iothread();
mem_access_callback_remove_by_ref(qemu_get_cpu(0), surface->access_cb);
qemu_mutex_unlock_iothread();
qemu_mutex_lock(&d->pgraph.lock);
}
glDeleteTextures(1, &surface->gl_buffer);
QTAILQ_REMOVE(&r->surfaces, surface, entry);
g_free(surface);
}
static void surface_evict_old(NV2AState *d)
{
PGRAPHState *pg = &d->pgraph;
PGRAPHGLState *r = pg->gl_renderer_state;
const int surface_age_limit = 5;
SurfaceBinding *s, *next;
QTAILQ_FOREACH_SAFE(s, &r->surfaces, entry, next) {
int last_used = d->pgraph.frame_time - s->frame_time;
if (last_used >= surface_age_limit) {
trace_nv2a_pgraph_surface_evict_reason("old", s->vram_addr);
pgraph_gl_surface_download_if_dirty(d, s);
pgraph_gl_surface_invalidate(d, s);
}
}
}
static bool check_surface_compatibility(SurfaceBinding *s1, SurfaceBinding *s2,
bool strict)
{
bool format_compatible =
(s1->color == s2->color) &&
(s1->fmt.gl_attachment == s2->fmt.gl_attachment) &&
(s1->fmt.gl_internal_format == s2->fmt.gl_internal_format) &&
(s1->pitch == s2->pitch) &&
(s1->shape.clip_x <= s2->shape.clip_x) &&
(s1->shape.clip_y <= s2->shape.clip_y);
if (!format_compatible) {
return false;
}
if (!strict) {
return (s1->width >= s2->width) && (s1->height >= s2->height);
} else {
return (s1->width == s2->width) && (s1->height == s2->height);
}
}
void pgraph_gl_surface_download_if_dirty(NV2AState *d,
SurfaceBinding *surface)
{
if (surface->draw_dirty) {
surface_download(d, surface, true);
}
}
static void bind_current_surface(NV2AState *d)
{
PGRAPHState *pg = &d->pgraph;
PGRAPHGLState *r = pg->gl_renderer_state;
if (r->color_binding) {
glFramebufferTexture2D(GL_FRAMEBUFFER, r->color_binding->fmt.gl_attachment,
GL_TEXTURE_2D, r->color_binding->gl_buffer, 0);
}
if (r->zeta_binding) {
glFramebufferTexture2D(GL_FRAMEBUFFER, r->zeta_binding->fmt.gl_attachment,
GL_TEXTURE_2D, r->zeta_binding->gl_buffer, 0);
}
if (r->color_binding || r->zeta_binding) {
assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) ==
GL_FRAMEBUFFER_COMPLETE);
}
}
static void surface_copy_shrink_row(uint8_t *out, uint8_t *in,
unsigned int width,
unsigned int bytes_per_pixel,
unsigned int factor)
{
if (bytes_per_pixel == 4) {
for (unsigned int x = 0; x < width; x++) {
*(uint32_t *)out = *(uint32_t *)in;
out += 4;
in += 4 * factor;
}
} else if (bytes_per_pixel == 2) {
for (unsigned int x = 0; x < width; x++) {
*(uint16_t *)out = *(uint16_t *)in;
out += 2;
in += 2 * factor;
}
} else {
for (unsigned int x = 0; x < width; x++) {
memcpy(out, in, bytes_per_pixel);
out += bytes_per_pixel;
in += bytes_per_pixel * factor;
}
}
}
static void surface_download_to_buffer(NV2AState *d, SurfaceBinding *surface,
bool swizzle, bool flip, bool downscale,
uint8_t *pixels)
{
PGRAPHState *pg = &d->pgraph;
swizzle &= surface->swizzle;
downscale &= (pg->surface_scale_factor != 1);
trace_nv2a_pgraph_surface_download(
surface->color ? "COLOR" : "ZETA",
surface->swizzle ? "sz" : "lin", surface->vram_addr,
surface->width, surface->height, surface->pitch,
surface->fmt.bytes_per_pixel);
/* Bind destination surface to framebuffer */
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
0, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
0, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
GL_TEXTURE_2D, 0, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, surface->fmt.gl_attachment,
GL_TEXTURE_2D, surface->gl_buffer, 0);
assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
/* Read surface into memory */
uint8_t *gl_read_buf = pixels;
uint8_t *swizzle_buf = pixels;
if (swizzle) {
/* FIXME: Allocate big buffer up front and re-alloc if necessary.
* FIXME: Consider swizzle in shader
*/
assert(pg->surface_scale_factor == 1 || downscale);
swizzle_buf = (uint8_t *)g_malloc(surface->size);
gl_read_buf = swizzle_buf;
}
if (downscale) {
pg->scale_buf = (uint8_t *)g_realloc(
pg->scale_buf, pg->surface_scale_factor * pg->surface_scale_factor *
surface->size);
gl_read_buf = pg->scale_buf;
}
glo_readpixels(
surface->fmt.gl_format, surface->fmt.gl_type, surface->fmt.bytes_per_pixel,
pg->surface_scale_factor * surface->pitch,
pg->surface_scale_factor * surface->width,
pg->surface_scale_factor * surface->height, flip, gl_read_buf);
/* FIXME: Replace this with a hw accelerated version */
if (downscale) {
assert(surface->pitch >= (surface->width * surface->fmt.bytes_per_pixel));
uint8_t *out = swizzle_buf, *in = pg->scale_buf;
for (unsigned int y = 0; y < surface->height; y++) {
surface_copy_shrink_row(out, in, surface->width,
surface->fmt.bytes_per_pixel,
pg->surface_scale_factor);
in += surface->pitch * pg->surface_scale_factor *
pg->surface_scale_factor;
out += surface->pitch;
}
}
if (swizzle) {
swizzle_rect(swizzle_buf, surface->width, surface->height, pixels,
surface->pitch, surface->fmt.bytes_per_pixel);
g_free(swizzle_buf);
}
/* Re-bind original framebuffer target */
glFramebufferTexture2D(GL_FRAMEBUFFER, surface->fmt.gl_attachment,
GL_TEXTURE_2D, 0, 0);
bind_current_surface(d);
}
static void surface_download(NV2AState *d, SurfaceBinding *surface, bool force)
{
if (!(surface->download_pending || force)) {
return;
}
/* FIXME: Respect write enable at last TOU? */
nv2a_profile_inc_counter(NV2A_PROF_SURF_DOWNLOAD);
surface_download_to_buffer(d, surface, true, true, true,
d->vram_ptr + surface->vram_addr);
memory_region_set_client_dirty(d->vram, surface->vram_addr,
surface->pitch * surface->height,
DIRTY_MEMORY_VGA);
memory_region_set_client_dirty(d->vram, surface->vram_addr,
surface->pitch * surface->height,
DIRTY_MEMORY_NV2A_TEX);
surface->download_pending = false;
surface->draw_dirty = false;
}
void pgraph_gl_process_pending_downloads(NV2AState *d)
{
PGRAPHState *pg = &d->pgraph;
PGRAPHGLState *r = pg->gl_renderer_state;
SurfaceBinding *surface;
QTAILQ_FOREACH(surface, &r->surfaces, entry) {
surface_download(d, surface, false);
}
qatomic_set(&r->downloads_pending, false);
qemu_event_set(&r->downloads_complete);
}
void pgraph_gl_download_dirty_surfaces(NV2AState *d)
{
PGRAPHState *pg = &d->pgraph;
PGRAPHGLState *r = pg->gl_renderer_state;
SurfaceBinding *surface;
QTAILQ_FOREACH(surface, &r->surfaces, entry) {
pgraph_gl_surface_download_if_dirty(d, surface);
}
qatomic_set(&r->download_dirty_surfaces_pending, false);
qemu_event_set(&r->dirty_surfaces_download_complete);
}
static void surface_copy_expand_row(uint8_t *out, uint8_t *in,
unsigned int width,
unsigned int bytes_per_pixel,
unsigned int factor)
{
if (bytes_per_pixel == 4) {
for (unsigned int x = 0; x < width; x++) {
for (unsigned int i = 0; i < factor; i++) {
*(uint32_t *)out = *(uint32_t *)in;
out += bytes_per_pixel;
}
in += bytes_per_pixel;
}
} else if (bytes_per_pixel == 2) {
for (unsigned int x = 0; x < width; x++) {
for (unsigned int i = 0; i < factor; i++) {
*(uint16_t *)out = *(uint16_t *)in;
out += bytes_per_pixel;
}
in += bytes_per_pixel;
}
} else {
for (unsigned int x = 0; x < width; x++) {
for (unsigned int i = 0; i < factor; i++) {
memcpy(out, in, bytes_per_pixel);
out += bytes_per_pixel;
}
in += bytes_per_pixel;
}
}
}
static void surface_copy_expand(uint8_t *out, uint8_t *in, unsigned int width,
unsigned int height,
unsigned int bytes_per_pixel,
unsigned int factor)
{
size_t out_pitch = width * bytes_per_pixel * factor;
for (unsigned int y = 0; y < height; y++) {
surface_copy_expand_row(out, in, width, bytes_per_pixel, factor);
uint8_t *row_in = out;
for (unsigned int i = 1; i < factor; i++) {
out += out_pitch;
memcpy(out, row_in, out_pitch);
}
in += width * bytes_per_pixel;
out += out_pitch;
}
}
void pgraph_gl_upload_surface_data(NV2AState *d, SurfaceBinding *surface,
bool force)
{
if (!(surface->upload_pending || force)) {
return;
}
nv2a_profile_inc_counter(NV2A_PROF_SURF_UPLOAD);
trace_nv2a_pgraph_surface_upload(
surface->color ? "COLOR" : "ZETA",
surface->swizzle ? "sz" : "lin", surface->vram_addr,
surface->width, surface->height, surface->pitch,
surface->fmt.bytes_per_pixel);
PGRAPHState *pg = &d->pgraph;
surface->upload_pending = false;
surface->draw_time = pg->draw_time;
// FIXME: Don't query GL for texture binding
GLint last_texture_binding;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture_binding);
// FIXME: Replace with FBO to not disturb current state
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
0, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
0, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
GL_TEXTURE_2D, 0, 0);
uint8_t *data = d->vram_ptr;
uint8_t *buf = data + surface->vram_addr;
if (surface->swizzle) {
buf = (uint8_t*)g_malloc(surface->size);
unswizzle_rect(data + surface->vram_addr,
surface->width, surface->height,
buf,
surface->pitch,
surface->fmt.bytes_per_pixel);
}
/* FIXME: Replace this flip/scaling */
// This is VRAM so we can't do this inplace!
uint8_t *flipped_buf = (uint8_t *)g_malloc(
surface->height * surface->width * surface->fmt.bytes_per_pixel);
unsigned int irow;
for (irow = 0; irow < surface->height; irow++) {
memcpy(&flipped_buf[surface->width * (surface->height - irow - 1)
* surface->fmt.bytes_per_pixel],
&buf[surface->pitch * irow],
surface->width * surface->fmt.bytes_per_pixel);
}
uint8_t *gl_read_buf = flipped_buf;
unsigned int width = surface->width, height = surface->height;
if (pg->surface_scale_factor > 1) {
pgraph_apply_scaling_factor(pg, &width, &height);
pg->scale_buf = (uint8_t *)g_realloc(
pg->scale_buf, width * height * surface->fmt.bytes_per_pixel);
gl_read_buf = pg->scale_buf;
uint8_t *out = gl_read_buf, *in = flipped_buf;
surface_copy_expand(out, in, surface->width, surface->height,
surface->fmt.bytes_per_pixel,
d->pgraph.surface_scale_factor);
}
int prev_unpack_alignment;
glGetIntegerv(GL_UNPACK_ALIGNMENT, &prev_unpack_alignment);
if (unlikely((width * surface->fmt.bytes_per_pixel) % 4 != 0)) {
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
} else {
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
}
glBindTexture(GL_TEXTURE_2D, surface->gl_buffer);
glTexImage2D(GL_TEXTURE_2D, 0, surface->fmt.gl_internal_format, width,
height, 0, surface->fmt.gl_format, surface->fmt.gl_type,
gl_read_buf);
glPixelStorei(GL_UNPACK_ALIGNMENT, prev_unpack_alignment);
g_free(flipped_buf);
if (surface->swizzle) {
g_free(buf);
}
// Rebind previous framebuffer binding
glBindTexture(GL_TEXTURE_2D, last_texture_binding);
bind_current_surface(d);
}
static void compare_surfaces(SurfaceBinding *s1, SurfaceBinding *s2)
{
#define DO_CMP(fld) \
if (s1->fld != s2->fld) \
trace_nv2a_pgraph_surface_compare_mismatch( \
#fld, (long int)s1->fld, (long int)s2->fld);
DO_CMP(shape.clip_x)
DO_CMP(shape.clip_width)
DO_CMP(shape.clip_y)
DO_CMP(shape.clip_height)
DO_CMP(gl_buffer)
DO_CMP(fmt.bytes_per_pixel)
DO_CMP(fmt.gl_attachment)
DO_CMP(fmt.gl_internal_format)
DO_CMP(fmt.gl_format)
DO_CMP(fmt.gl_type)
DO_CMP(color)
DO_CMP(swizzle)
DO_CMP(vram_addr)
DO_CMP(width)
DO_CMP(height)
DO_CMP(pitch)
DO_CMP(size)
DO_CMP(dma_addr)
DO_CMP(dma_len)
DO_CMP(frame_time)
DO_CMP(draw_time)
#undef DO_CMP
}
static void populate_surface_binding_entry_sized(NV2AState *d, bool color,
unsigned int width,
unsigned int height,
SurfaceBinding *entry)
{
PGRAPHState *pg = &d->pgraph;
PGRAPHGLState *r = pg->gl_renderer_state;
Surface *surface;
hwaddr dma_address;
SurfaceFormatInfo fmt;
if (color) {
surface = &pg->surface_color;
dma_address = pg->dma_color;
assert(pg->surface_shape.color_format != 0);
assert(pg->surface_shape.color_format <
ARRAY_SIZE(kelvin_surface_color_format_gl_map));
fmt = kelvin_surface_color_format_gl_map[pg->surface_shape.color_format];
if (fmt.bytes_per_pixel == 0) {
fprintf(stderr, "nv2a: unimplemented color surface format 0x%x\n",
pg->surface_shape.color_format);
abort();
}
} else {
surface = &pg->surface_zeta;
dma_address = pg->dma_zeta;
assert(pg->surface_shape.zeta_format != 0);
assert(pg->surface_shape.zeta_format <
ARRAY_SIZE(kelvin_surface_zeta_float_format_gl_map));
const SurfaceFormatInfo *map =
pg->surface_shape.z_format ? kelvin_surface_zeta_float_format_gl_map :
kelvin_surface_zeta_fixed_format_gl_map;
fmt = map[pg->surface_shape.zeta_format];
}
DMAObject dma = nv_dma_load(d, dma_address);
/* There's a bunch of bugs that could cause us to hit this function
* at the wrong time and get a invalid dma object.
* Check that it's sane. */
assert(dma.dma_class == NV_DMA_IN_MEMORY_CLASS);
// assert(dma.address + surface->offset != 0);
assert(surface->offset <= dma.limit);
assert(surface->offset + surface->pitch * height <= dma.limit + 1);
assert(surface->pitch % fmt.bytes_per_pixel == 0);
assert((dma.address & ~0x07FFFFFF) == 0);
entry->shape = (color || !r->color_binding) ? pg->surface_shape :
r->color_binding->shape;
entry->gl_buffer = 0;
entry->fmt = fmt;
entry->color = color;
entry->swizzle =
(pg->surface_type == NV097_SET_SURFACE_FORMAT_TYPE_SWIZZLE);
entry->vram_addr = dma.address + surface->offset;
entry->width = width;
entry->height = height;
entry->pitch = surface->pitch;
entry->size = height * MAX(surface->pitch, width * fmt.bytes_per_pixel);
entry->upload_pending = true;
entry->download_pending = false;
entry->draw_dirty = false;
entry->dma_addr = dma.address;
entry->dma_len = dma.limit;
entry->frame_time = pg->frame_time;
entry->draw_time = pg->draw_time;
entry->cleared = false;
}
static void populate_surface_binding_entry(NV2AState *d, bool color,
SurfaceBinding *entry)
{
PGRAPHState *pg = &d->pgraph;
PGRAPHGLState *r = pg->gl_renderer_state;
unsigned int width, height;
if (color || !r->color_binding) {
surface_get_dimensions(pg, &width, &height);
pgraph_apply_anti_aliasing_factor(pg, &width, &height);
/* Since we determine surface dimensions based on the clipping
* rectangle, make sure to include the surface offset as well.
*/
if (pg->surface_type != NV097_SET_SURFACE_FORMAT_TYPE_SWIZZLE) {
width += pg->surface_shape.clip_x;
height += pg->surface_shape.clip_y;
}
} else {
width = r->color_binding->width;
height = r->color_binding->height;
}
populate_surface_binding_entry_sized(d, color, width, height, entry);
}
static void update_surface_part(NV2AState *d, bool upload, bool color)
{
PGRAPHState *pg = &d->pgraph;
PGRAPHGLState *r = pg->gl_renderer_state;
SurfaceBinding entry;
populate_surface_binding_entry(d, color, &entry);
Surface *surface = color ? &pg->surface_color : &pg->surface_zeta;
bool mem_dirty = !tcg_enabled() && memory_region_test_and_clear_dirty(
d->vram, entry.vram_addr, entry.size,
DIRTY_MEMORY_NV2A);
if (upload && (surface->buffer_dirty || mem_dirty)) {
pgraph_gl_unbind_surface(d, color);
SurfaceBinding *found = pgraph_gl_surface_get(d, entry.vram_addr);
if (found != NULL) {
/* FIXME: Support same color/zeta surface target? In the mean time,
* if the surface we just found is currently bound, just unbind it.
*/
SurfaceBinding *other = (color ? r->zeta_binding
: r->color_binding);
if (found == other) {
NV2A_UNIMPLEMENTED("Same color & zeta surface offset");
pgraph_gl_unbind_surface(d, !color);
}
}
trace_nv2a_pgraph_surface_target(
color ? "COLOR" : "ZETA", entry.vram_addr,
entry.swizzle ? "sz" : "ln",
pg->surface_shape.anti_aliasing,
pg->surface_shape.clip_x,
pg->surface_shape.clip_width, pg->surface_shape.clip_y,
pg->surface_shape.clip_height);
bool should_create = true;
if (found != NULL) {
bool is_compatible =
check_surface_compatibility(found, &entry, false);
#define TRACE_ARGS found->vram_addr, found->width, found->height, \
found->swizzle ? "sz" : "ln", \
found->shape.anti_aliasing, found->shape.clip_x, \
found->shape.clip_width, found->shape.clip_y, \
found->shape.clip_height, found->pitch
if (found->color) {
trace_nv2a_pgraph_surface_match_color(TRACE_ARGS);
} else {
trace_nv2a_pgraph_surface_match_zeta(TRACE_ARGS);
}
#undef TRACE_ARGS
assert(!(entry.swizzle && pg->clearing));
if (found->swizzle != entry.swizzle) {
/* Clears should only be done on linear surfaces. Avoid
* synchronization by allowing (1) a surface marked swizzled to
* be cleared under the assumption the entire surface is
* destined to be cleared and (2) a fully cleared linear surface
* to be marked swizzled. Strictly match size to avoid
* pathological cases.
*/
is_compatible &= (pg->clearing || found->cleared) &&
check_surface_compatibility(found, &entry, true);
if (is_compatible) {
trace_nv2a_pgraph_surface_migrate_type(
entry.swizzle ? "swizzled" : "linear");
}
}
if (is_compatible && color &&
!check_surface_compatibility(found, &entry, true)) {
SurfaceBinding zeta_entry;
populate_surface_binding_entry_sized(
d, !color, found->width, found->height, &zeta_entry);
hwaddr color_end = found->vram_addr + found->size;
hwaddr zeta_end = zeta_entry.vram_addr + zeta_entry.size;
is_compatible &= found->vram_addr >= zeta_end ||
zeta_entry.vram_addr >= color_end;
}
if (is_compatible && !color && r->color_binding) {
is_compatible &= (found->width == r->color_binding->width) &&
(found->height == r->color_binding->height);
}
if (is_compatible) {
/* FIXME: Refactor */
pg->surface_binding_dim.width = found->width;
pg->surface_binding_dim.clip_x = found->shape.clip_x;
pg->surface_binding_dim.clip_width = found->shape.clip_width;
pg->surface_binding_dim.height = found->height;
pg->surface_binding_dim.clip_y = found->shape.clip_y;
pg->surface_binding_dim.clip_height = found->shape.clip_height;
found->upload_pending |= mem_dirty;
pg->surface_zeta.buffer_dirty |= color;
should_create = false;
} else {
trace_nv2a_pgraph_surface_evict_reason(
"incompatible", found->vram_addr);
compare_surfaces(found, &entry);
pgraph_gl_surface_download_if_dirty(d, found);
pgraph_gl_surface_invalidate(d, found);
}
}
if (should_create) {
glGenTextures(1, &entry.gl_buffer);
glBindTexture(GL_TEXTURE_2D, entry.gl_buffer);
NV2A_GL_DLABEL(GL_TEXTURE, entry.gl_buffer,
"%s format: %0X, width: %d, height: %d "
"(addr %" HWADDR_PRIx ")",
color ? "color" : "zeta",
color ? pg->surface_shape.color_format
: pg->surface_shape.zeta_format,
entry.width, entry.height, surface->offset);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
unsigned int width = entry.width, height = entry.height;
pgraph_apply_scaling_factor(pg, &width, &height);
glTexImage2D(GL_TEXTURE_2D, 0, entry.fmt.gl_internal_format, width,
height, 0, entry.fmt.gl_format, entry.fmt.gl_type,
NULL);
found = surface_put(d, entry.vram_addr, &entry);
/* FIXME: Refactor */
pg->surface_binding_dim.width = entry.width;
pg->surface_binding_dim.clip_x = entry.shape.clip_x;
pg->surface_binding_dim.clip_width = entry.shape.clip_width;
pg->surface_binding_dim.height = entry.height;
pg->surface_binding_dim.clip_y = entry.shape.clip_y;
pg->surface_binding_dim.clip_height = entry.shape.clip_height;
if (color && r->zeta_binding && (r->zeta_binding->width != entry.width || r->zeta_binding->height != entry.height)) {
pg->surface_zeta.buffer_dirty = true;
}
}
#define TRACE_ARGS found->vram_addr, found->width, found->height, \
found->swizzle ? "sz" : "ln", found->shape.anti_aliasing, \
found->shape.clip_x, found->shape.clip_width, \
found->shape.clip_y, found->shape.clip_height, found->pitch
if (color) {
if (should_create) {
trace_nv2a_pgraph_surface_create_color(TRACE_ARGS);
} else {
trace_nv2a_pgraph_surface_hit_color(TRACE_ARGS);
}
r->color_binding = found;
} else {
if (should_create) {
trace_nv2a_pgraph_surface_create_zeta(TRACE_ARGS);
} else {
trace_nv2a_pgraph_surface_hit_zeta(TRACE_ARGS);
}
r->zeta_binding = found;
}
#undef TRACE_ARGS
glFramebufferTexture2D(GL_FRAMEBUFFER, entry.fmt.gl_attachment,
GL_TEXTURE_2D, found->gl_buffer, 0);
assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) ==
GL_FRAMEBUFFER_COMPLETE);
surface->buffer_dirty = false;
}
if (!upload && surface->draw_dirty) {
if (!tcg_enabled()) {
/* FIXME: Cannot monitor for reads/writes; flush now */
surface_download(d,
color ? r->color_binding :
r->zeta_binding,
true);
}
surface->write_enabled_cache = false;
surface->draw_dirty = false;
}
}
void pgraph_gl_unbind_surface(NV2AState *d, bool color)
{
PGRAPHState *pg = &d->pgraph;
PGRAPHGLState *r = pg->gl_renderer_state;
if (color) {
if (r->color_binding) {
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, 0, 0);
r->color_binding = NULL;
}
} else {
if (r->zeta_binding) {
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_DEPTH_ATTACHMENT,
GL_TEXTURE_2D, 0, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_DEPTH_STENCIL_ATTACHMENT,
GL_TEXTURE_2D, 0, 0);
r->zeta_binding = NULL;
}
}
}
void pgraph_gl_surface_update(NV2AState *d, bool upload, bool color_write,
bool zeta_write)
{
PGRAPHState *pg = &d->pgraph;
PGRAPHGLState *r = pg->gl_renderer_state;
pg->surface_shape.z_format =
GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER),
NV_PGRAPH_SETUPRASTER_Z_FORMAT);
color_write = color_write &&
(pg->clearing || pgraph_color_write_enabled(pg));
zeta_write = zeta_write && (pg->clearing || pgraph_zeta_write_enabled(pg));
if (upload) {
bool fb_dirty = framebuffer_dirty(pg);
if (fb_dirty) {
memcpy(&pg->last_surface_shape, &pg->surface_shape,
sizeof(SurfaceShape));
pg->surface_color.buffer_dirty = true;
pg->surface_zeta.buffer_dirty = true;
}
if (pg->surface_color.buffer_dirty) {
pgraph_gl_unbind_surface(d, true);
}
if (color_write) {
update_surface_part(d, true, true);
}
if (pg->surface_zeta.buffer_dirty) {
pgraph_gl_unbind_surface(d, false);
}
if (zeta_write) {
update_surface_part(d, true, false);
}
} else {
if ((color_write || pg->surface_color.write_enabled_cache)
&& pg->surface_color.draw_dirty) {
update_surface_part(d, false, true);
}
if ((zeta_write || pg->surface_zeta.write_enabled_cache)
&& pg->surface_zeta.draw_dirty) {
update_surface_part(d, false, false);
}
}
if (upload) {
pg->draw_time++;
}
bool swizzle = (pg->surface_type == NV097_SET_SURFACE_FORMAT_TYPE_SWIZZLE);
if (r->color_binding) {
r->color_binding->frame_time = pg->frame_time;
if (upload) {
pgraph_gl_upload_surface_data(d, r->color_binding, false);
r->color_binding->draw_time = pg->draw_time;
r->color_binding->swizzle = swizzle;
}
}
if (r->zeta_binding) {
r->zeta_binding->frame_time = pg->frame_time;
if (upload) {
pgraph_gl_upload_surface_data(d, r->zeta_binding, false);
r->zeta_binding->draw_time = pg->draw_time;
r->zeta_binding->swizzle = swizzle;
}
}
// Sanity check color and zeta dimensions match
if (r->color_binding && r->zeta_binding) {
assert((r->color_binding->width == r->zeta_binding->width)
&& (r->color_binding->height == r->zeta_binding->height));
}
surface_evict_old(d);
}
// FIXME: Move to common
static void surface_get_dimensions(PGRAPHState *pg, unsigned int *width,
unsigned int *height)
{
bool swizzle = (pg->surface_type == NV097_SET_SURFACE_FORMAT_TYPE_SWIZZLE);
if (swizzle) {
*width = 1 << pg->surface_shape.log_width;
*height = 1 << pg->surface_shape.log_height;
} else {
*width = pg->surface_shape.clip_width;
*height = pg->surface_shape.clip_height;
}
}
void pgraph_gl_init_surfaces(PGRAPHState *pg)
{
PGRAPHGLState *r = pg->gl_renderer_state;
pgraph_gl_reload_surface_scale_factor(pg);
glGenFramebuffers(1, &r->gl_framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, r->gl_framebuffer);
QTAILQ_INIT(&r->surfaces);
r->downloads_pending = false;
qemu_event_init(&r->downloads_complete, false);
qemu_event_init(&r->dirty_surfaces_download_complete, false);
init_render_to_texture(pg);
}
static void flush_surfaces(NV2AState *d)
{
PGRAPHState *pg = &d->pgraph;
PGRAPHGLState *r = pg->gl_renderer_state;
/* Clear last surface shape to force recreation of buffers at next draw */
pg->surface_color.draw_dirty = false;
pg->surface_zeta.draw_dirty = false;
memset(&pg->last_surface_shape, 0, sizeof(pg->last_surface_shape));
pgraph_gl_unbind_surface(d, true);
pgraph_gl_unbind_surface(d, false);
SurfaceBinding *s, *next;
QTAILQ_FOREACH_SAFE(s, &r->surfaces, entry, next) {
// FIXME: We should download all surfaces to ram, but need to
// investigate corruption issue
// pgraph_gl_surface_download_if_dirty(d, s);
pgraph_gl_surface_invalidate(d, s);
}
}
void pgraph_gl_finalize_surfaces(PGRAPHState *pg)
{
NV2AState *d = container_of(pg, NV2AState, pgraph);
PGRAPHGLState *r = pg->gl_renderer_state;
flush_surfaces(d);
glDeleteFramebuffers(1, &r->gl_framebuffer);
r->gl_framebuffer = 0;
finalize_render_to_texture(pg);
}
void pgraph_gl_surface_flush(NV2AState *d)
{
PGRAPHState *pg = &d->pgraph;
PGRAPHGLState *r = pg->gl_renderer_state;
bool update_surface = (r->color_binding || r->zeta_binding);
flush_surfaces(d);
pgraph_gl_reload_surface_scale_factor(pg);
if (update_surface) {
pgraph_gl_surface_update(d, true, true, true);
}
}