xemu/hw/xbox/nv2a/pgraph/texture.c

406 lines
16 KiB
C

/*
* QEMU Geforce NV2A implementation
*
* Copyright (c) 2012 espes
* Copyright (c) 2015 Jannik Vogel
* Copyright (c) 2018-2025 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/nv2a_int.h"
#include "texture.h"
#include "util.h"
const BasicColorFormatInfo kelvin_color_format_info_map[66] = {
[NV097_SET_TEXTURE_FORMAT_COLOR_SZ_Y8] = { 1, false },
[NV097_SET_TEXTURE_FORMAT_COLOR_SZ_AY8] = { 1, false },
[NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A1R5G5B5] = { 2, false },
[NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X1R5G5B5] = { 2, false },
[NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A4R4G4B4] = { 2, false },
[NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R5G6B5] = { 2, false },
[NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8R8G8B8] = { 4, false },
[NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X8R8G8B8] = { 4, false },
[NV097_SET_TEXTURE_FORMAT_COLOR_SZ_I8_A8R8G8B8] = { 1, false },
[NV097_SET_TEXTURE_FORMAT_COLOR_L_DXT1_A1R5G5B5] = { 4, false },
[NV097_SET_TEXTURE_FORMAT_COLOR_L_DXT23_A8R8G8B8] = { 4, false },
[NV097_SET_TEXTURE_FORMAT_COLOR_L_DXT45_A8R8G8B8] = { 4, false },
[NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A1R5G5B5] = { 2, true },
[NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_R5G6B5] = { 2, true },
[NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8R8G8B8] = { 4, true },
[NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_Y8] = { 1, true },
[NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_G8B8] = { 2, true },
[NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8] = { 1, false },
[NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8Y8] = { 2, false },
[NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_AY8] = { 1, true },
[NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X1R5G5B5] = { 2, true },
[NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A4R4G4B4] = { 2, true },
[NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X8R8G8B8] = { 4, true },
[NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8] = { 1, true },
[NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8Y8] = { 2, true },
[NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R6G5B5] = { 2, false },
[NV097_SET_TEXTURE_FORMAT_COLOR_SZ_G8B8] = { 2, false },
[NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R8B8] = { 2, false },
[NV097_SET_TEXTURE_FORMAT_COLOR_LC_IMAGE_CR8YB8CB8YA8] = { 2, true },
[NV097_SET_TEXTURE_FORMAT_COLOR_LC_IMAGE_YB8CR8YA8CB8] = { 2, true },
[NV097_SET_TEXTURE_FORMAT_COLOR_SZ_DEPTH_Y16_FIXED] = { 2, false, true },
[NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_DEPTH_X8_Y24_FIXED] = { 4, true,
true },
[NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_DEPTH_X8_Y24_FLOAT] = { 4, true,
true },
[NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_DEPTH_Y16_FIXED] = { 2, true,
true },
[NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_DEPTH_Y16_FLOAT] = { 2, true,
true },
[NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_Y16] = { 2, true },
[NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8B8G8R8] = { 4, false },
[NV097_SET_TEXTURE_FORMAT_COLOR_SZ_B8G8R8A8] = { 4, false },
[NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R8G8B8A8] = { 4, false },
[NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8B8G8R8] = { 4, true },
[NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_B8G8R8A8] = { 4, true },
[NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_R8G8B8A8] = { 4, true },
};
hwaddr pgraph_get_texture_phys_addr(PGRAPHState *pg, int texture_idx)
{
NV2AState *d = container_of(pg, NV2AState, pgraph);
int i = texture_idx;
uint32_t fmt = pgraph_reg_r(pg, NV_PGRAPH_TEXFMT0 + i*4);
unsigned int dma_select =
GET_MASK(fmt, NV_PGRAPH_TEXFMT0_CONTEXT_DMA);
hwaddr offset = pgraph_reg_r(pg, NV_PGRAPH_TEXOFFSET0 + i*4);
hwaddr dma_len;
uint8_t *texture_data;
if (dma_select) {
texture_data = (uint8_t*)nv_dma_map(d, pg->dma_b, &dma_len);
} else {
texture_data = (uint8_t*)nv_dma_map(d, pg->dma_a, &dma_len);
}
assert(offset < dma_len);
texture_data += offset;
return texture_data - d->vram_ptr;
}
hwaddr pgraph_get_texture_palette_phys_addr_length(PGRAPHState *pg, int texture_idx, size_t *length)
{
NV2AState *d = container_of(pg, NV2AState, pgraph);
int i = texture_idx;
uint32_t palette = pgraph_reg_r(pg, NV_PGRAPH_TEXPALETTE0 + i*4);
bool palette_dma_select =
GET_MASK(palette, NV_PGRAPH_TEXPALETTE0_CONTEXT_DMA);
unsigned int palette_length_index =
GET_MASK(palette, NV_PGRAPH_TEXPALETTE0_LENGTH);
unsigned int palette_offset =
palette & NV_PGRAPH_TEXPALETTE0_OFFSET;
unsigned int palette_length = 0;
switch (palette_length_index) {
case NV_PGRAPH_TEXPALETTE0_LENGTH_256: palette_length = 256; break;
case NV_PGRAPH_TEXPALETTE0_LENGTH_128: palette_length = 128; break;
case NV_PGRAPH_TEXPALETTE0_LENGTH_64: palette_length = 64; break;
case NV_PGRAPH_TEXPALETTE0_LENGTH_32: palette_length = 32; break;
default: assert(false); break;
}
if (length) {
*length = palette_length;
}
hwaddr palette_dma_len;
uint8_t *palette_data;
if (palette_dma_select) {
palette_data = (uint8_t*)nv_dma_map(d, pg->dma_b, &palette_dma_len);
} else {
palette_data = (uint8_t*)nv_dma_map(d, pg->dma_a, &palette_dma_len);
}
assert(palette_offset < palette_dma_len);
palette_data += palette_offset;
return palette_data - d->vram_ptr;
}
size_t pgraph_get_texture_length(PGRAPHState *pg, TextureShape *shape)
{
BasicColorFormatInfo f = kelvin_color_format_info_map[shape->color_format];
size_t length = 0;
if (f.linear) {
assert(shape->cubemap == false);
assert(shape->dimensionality == 2);
length = shape->height * shape->pitch;
} else {
if (shape->dimensionality >= 2) {
unsigned int w = shape->width, h = shape->height;
int level;
if (!pgraph_is_texture_format_compressed(pg, shape->color_format)) {
for (level = 0; level < shape->levels; level++) {
w = MAX(w, 1);
h = MAX(h, 1);
length += w * h * f.bytes_per_pixel;
w /= 2;
h /= 2;
}
} else {
/* Compressed textures are a bit different */
unsigned int block_size =
shape->color_format ==
NV097_SET_TEXTURE_FORMAT_COLOR_L_DXT1_A1R5G5B5 ?
8 : 16;
for (level = 0; level < shape->levels; level++) {
w = MAX(w, 1);
h = MAX(h, 1);
unsigned int phys_w = (w + 3) & ~3,
phys_h = (h + 3) & ~3;
length += phys_w/4 * phys_h/4 * block_size;
w /= 2;
h /= 2;
}
}
if (shape->cubemap) {
assert(shape->dimensionality == 2);
length = (length + NV2A_CUBEMAP_FACE_ALIGNMENT - 1) & ~(NV2A_CUBEMAP_FACE_ALIGNMENT - 1);
length *= 6;
}
if (shape->dimensionality >= 3) {
length *= shape->depth;
}
}
}
return length;
}
TextureShape pgraph_get_texture_shape(PGRAPHState *pg, int texture_idx)
{
int i = texture_idx;
uint32_t ctl_0 = pgraph_reg_r(pg, NV_PGRAPH_TEXCTL0_0 + i*4);
uint32_t ctl_1 = pgraph_reg_r(pg, NV_PGRAPH_TEXCTL1_0 + i*4);
uint32_t fmt = pgraph_reg_r(pg, NV_PGRAPH_TEXFMT0 + i*4);
#if DEBUG_NV2A
uint32_t filter = pgraph_reg_r(pg, NV_PGRAPH_TEXFILTER0 + i*4);
uint32_t address = pgraph_reg_r(pg, NV_PGRAPH_TEXADDRESS0 + i*4);
#endif
unsigned int min_mipmap_level =
GET_MASK(ctl_0, NV_PGRAPH_TEXCTL0_0_MIN_LOD_CLAMP);
unsigned int max_mipmap_level =
GET_MASK(ctl_0, NV_PGRAPH_TEXCTL0_0_MAX_LOD_CLAMP);
unsigned int pitch =
GET_MASK(ctl_1, NV_PGRAPH_TEXCTL1_0_IMAGE_PITCH);
bool cubemap =
GET_MASK(fmt, NV_PGRAPH_TEXFMT0_CUBEMAPENABLE);
unsigned int dimensionality =
GET_MASK(fmt, NV_PGRAPH_TEXFMT0_DIMENSIONALITY);
int tex_mode = (pgraph_reg_r(pg, NV_PGRAPH_SHADERPROG) >> (texture_idx * 5)) & 0x1F;
if (tex_mode == 0x02) {
assert(pgraph_is_texture_enabled(pg, texture_idx));
// assert(state.dimensionality == 3);
// OVERRIDE
// dimensionality = 3;
}
unsigned int color_format = GET_MASK(fmt, NV_PGRAPH_TEXFMT0_COLOR);
unsigned int levels = GET_MASK(fmt, NV_PGRAPH_TEXFMT0_MIPMAP_LEVELS);
unsigned int log_width = GET_MASK(fmt, NV_PGRAPH_TEXFMT0_BASE_SIZE_U);
unsigned int log_height = GET_MASK(fmt, NV_PGRAPH_TEXFMT0_BASE_SIZE_V);
unsigned int log_depth = GET_MASK(fmt, NV_PGRAPH_TEXFMT0_BASE_SIZE_P);
unsigned int rect_width =
GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_TEXIMAGERECT0 + i*4),
NV_PGRAPH_TEXIMAGERECT0_WIDTH);
unsigned int rect_height =
GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_TEXIMAGERECT0 + i*4),
NV_PGRAPH_TEXIMAGERECT0_HEIGHT);
#if DEBUG_NV2A
unsigned int lod_bias =
GET_MASK(filter, NV_PGRAPH_TEXFILTER0_MIPMAP_LOD_BIAS);
#endif
unsigned int border_source = GET_MASK(fmt,
NV_PGRAPH_TEXFMT0_BORDER_SOURCE);
NV2A_DPRINTF(" texture %d is format 0x%x, "
"off 0x%" HWADDR_PRIx " (r %d, %d or %d, %d, %d; %d%s),"
" filter %x %x, levels %d-%d %d bias %d\n",
i, color_format, address,
rect_width, rect_height,
1 << log_width, 1 << log_height, 1 << log_depth,
pitch,
cubemap ? "; cubemap" : "",
GET_MASK(filter, NV_PGRAPH_TEXFILTER0_MIN),
GET_MASK(filter, NV_PGRAPH_TEXFILTER0_MAG),
min_mipmap_level, max_mipmap_level, levels,
lod_bias);
assert(color_format < ARRAY_SIZE(kelvin_color_format_info_map));
BasicColorFormatInfo f = kelvin_color_format_info_map[color_format];
if (f.bytes_per_pixel == 0) {
fprintf(stderr, "nv2a: unimplemented texture color format 0x%x\n",
color_format);
abort();
}
unsigned int width, height, depth;
if (f.linear) {
assert(dimensionality == 2);
width = rect_width;
height = rect_height;
depth = 1;
} else {
width = 1 << log_width;
height = 1 << log_height;
depth = 1 << log_depth;
pitch = 0;
levels = MIN(levels, max_mipmap_level + 1);
/* Discard mipmap levels that would be smaller than 1x1.
* FIXME: Is this actually needed?
*
* >> Level 0: 32 x 4
* Level 1: 16 x 2
* Level 2: 8 x 1
* Level 3: 4 x 1
* Level 4: 2 x 1
* Level 5: 1 x 1
*/
levels = MIN(levels, MAX(log_width, log_height) + 1);
assert(levels > 0);
if (dimensionality == 3) {
/* FIXME: What about 3D mipmaps? */
if (log_width < 2 || log_height < 2) {
/* Base level is smaller than 4x4... */
levels = 1;
} else {
levels = MIN(levels, MIN(log_width, log_height) - 1);
}
}
min_mipmap_level = MIN(levels-1, min_mipmap_level);
max_mipmap_level = MIN(levels-1, max_mipmap_level);
}
TextureShape shape;
// We will hash it, so make sure any padding is zero
memset(&shape, 0, sizeof(shape));
shape.cubemap = cubemap;
shape.dimensionality = dimensionality;
shape.color_format = color_format;
shape.levels = levels;
shape.width = width;
shape.height = height;
shape.depth = depth;
shape.min_mipmap_level = min_mipmap_level;
shape.max_mipmap_level = max_mipmap_level;
shape.pitch = pitch;
shape.border = border_source != NV_PGRAPH_TEXFMT0_BORDER_SOURCE_COLOR;
return shape;
}
uint8_t *pgraph_convert_texture_data(const TextureShape s, const uint8_t *data,
const uint8_t *palette_data,
unsigned int width, unsigned int height,
unsigned int depth, unsigned int row_pitch,
unsigned int slice_pitch,
size_t *converted_size)
{
size_t size = 0;
uint8_t *converted_data;
if (s.color_format == NV097_SET_TEXTURE_FORMAT_COLOR_SZ_I8_A8R8G8B8) {
size = width * height * depth * 4;
converted_data = g_malloc(size);
const uint8_t *src = data;
uint32_t *dst = (uint32_t *)converted_data;
for (int z = 0; z < depth; z++) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint8_t index = src[y * row_pitch + x];
uint32_t color = *(uint32_t *)(palette_data + index * 4);
*dst++ = color;
}
}
src += slice_pitch;
}
} else if (s.color_format ==
NV097_SET_TEXTURE_FORMAT_COLOR_LC_IMAGE_CR8YB8CB8YA8 ||
s.color_format ==
NV097_SET_TEXTURE_FORMAT_COLOR_LC_IMAGE_YB8CR8YA8CB8) {
// TODO: Investigate whether a non-1 depth is possible.
// Generally the hardware asserts when attempting to use volumetric
// textures in linear formats.
assert(depth == 1); /* FIXME */
// FIXME: only valid if control0 register allows for colorspace
// conversion
size = width * height * 4;
converted_data = g_malloc(size);
uint8_t *pixel = converted_data;
for (int y = 0; y < height; y++) {
const uint8_t *line = &data[y * row_pitch * depth];
for (int x = 0; x < width; x++, pixel += 4) {
if (s.color_format ==
NV097_SET_TEXTURE_FORMAT_COLOR_LC_IMAGE_CR8YB8CB8YA8) {
convert_yuy2_to_rgb(line, x, &pixel[0], &pixel[1],
&pixel[2]);
} else {
convert_uyvy_to_rgb(line, x, &pixel[0], &pixel[1],
&pixel[2]);
}
pixel[3] = 255;
}
}
} else if (s.color_format == NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R6G5B5) {
assert(depth == 1); /* FIXME */
size = width * height * 3;
converted_data = g_malloc(size);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint16_t rgb655 = *(uint16_t *)(data + y * row_pitch + x * 2);
int8_t *pixel = (int8_t *)&converted_data[(y * width + x) * 3];
/* Maps 5 bit G and B signed value range to 8 bit
* signed values. R is probably unsigned.
*/
rgb655 ^= (1 << 9) | (1 << 4);
pixel[0] = ((rgb655 & 0xFC00) >> 10) * 0x7F / 0x3F;
pixel[1] = ((rgb655 & 0x03E0) >> 5) * 0xFF / 0x1F - 0x80;
pixel[2] = (rgb655 & 0x001F) * 0xFF / 0x1F - 0x80;
}
}
} else {
return NULL;
}
if (converted_size) {
*converted_size = size;
}
return converted_data;
}