1 // Copyright 2019 yuzu Emulator Project
2 // Licensed under GPLv2 or any later version
3 // Refer to the license.txt file included.
4 
5 #include <algorithm>
6 #include <string>
7 #include <tuple>
8 
9 #include "common/alignment.h"
10 #include "common/bit_util.h"
11 #include "core/core.h"
12 #include "video_core/engines/shader_bytecode.h"
13 #include "video_core/surface.h"
14 #include "video_core/texture_cache/format_lookup_table.h"
15 #include "video_core/texture_cache/surface_params.h"
16 
17 namespace VideoCommon {
18 
19 using VideoCore::Surface::PixelFormat;
20 using VideoCore::Surface::PixelFormatFromDepthFormat;
21 using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
22 using VideoCore::Surface::SurfaceTarget;
23 using VideoCore::Surface::SurfaceTargetFromTextureType;
24 using VideoCore::Surface::SurfaceType;
25 
26 namespace {
27 
TextureTypeToSurfaceTarget(Tegra::Shader::TextureType type,bool is_array)28 SurfaceTarget TextureTypeToSurfaceTarget(Tegra::Shader::TextureType type, bool is_array) {
29     switch (type) {
30     case Tegra::Shader::TextureType::Texture1D:
31         return is_array ? SurfaceTarget::Texture1DArray : SurfaceTarget::Texture1D;
32     case Tegra::Shader::TextureType::Texture2D:
33         return is_array ? SurfaceTarget::Texture2DArray : SurfaceTarget::Texture2D;
34     case Tegra::Shader::TextureType::Texture3D:
35         ASSERT(!is_array);
36         return SurfaceTarget::Texture3D;
37     case Tegra::Shader::TextureType::TextureCube:
38         return is_array ? SurfaceTarget::TextureCubeArray : SurfaceTarget::TextureCubemap;
39     default:
40         UNREACHABLE();
41         return SurfaceTarget::Texture2D;
42     }
43 }
44 
ImageTypeToSurfaceTarget(Tegra::Shader::ImageType type)45 SurfaceTarget ImageTypeToSurfaceTarget(Tegra::Shader::ImageType type) {
46     switch (type) {
47     case Tegra::Shader::ImageType::Texture1D:
48         return SurfaceTarget::Texture1D;
49     case Tegra::Shader::ImageType::TextureBuffer:
50         return SurfaceTarget::TextureBuffer;
51     case Tegra::Shader::ImageType::Texture1DArray:
52         return SurfaceTarget::Texture1DArray;
53     case Tegra::Shader::ImageType::Texture2D:
54         return SurfaceTarget::Texture2D;
55     case Tegra::Shader::ImageType::Texture2DArray:
56         return SurfaceTarget::Texture2DArray;
57     case Tegra::Shader::ImageType::Texture3D:
58         return SurfaceTarget::Texture3D;
59     default:
60         UNREACHABLE();
61         return SurfaceTarget::Texture2D;
62     }
63 }
64 
GetMipmapSize(bool uncompressed,u32 mip_size,u32 tile)65 constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) {
66     return uncompressed ? mip_size : std::max(1U, (mip_size + tile - 1) / tile);
67 }
68 
69 } // Anonymous namespace
70 
CreateForTexture(const FormatLookupTable & lookup_table,const Tegra::Texture::TICEntry & tic,const VideoCommon::Shader::Sampler & entry)71 SurfaceParams SurfaceParams::CreateForTexture(const FormatLookupTable& lookup_table,
72                                               const Tegra::Texture::TICEntry& tic,
73                                               const VideoCommon::Shader::Sampler& entry) {
74     SurfaceParams params;
75     params.is_tiled = tic.IsTiled();
76     params.srgb_conversion = tic.IsSrgbConversionEnabled();
77     params.block_width = params.is_tiled ? tic.BlockWidth() : 0;
78     params.block_height = params.is_tiled ? tic.BlockHeight() : 0;
79     params.block_depth = params.is_tiled ? tic.BlockDepth() : 0;
80     params.tile_width_spacing = params.is_tiled ? (1 << tic.tile_width_spacing.Value()) : 1;
81     params.pixel_format = lookup_table.GetPixelFormat(
82         tic.format, params.srgb_conversion, tic.r_type, tic.g_type, tic.b_type, tic.a_type);
83     params.type = GetFormatType(params.pixel_format);
84     if (entry.is_shadow && params.type == SurfaceType::ColorTexture) {
85         switch (params.pixel_format) {
86         case PixelFormat::R16_UNORM:
87         case PixelFormat::R16_FLOAT:
88             params.pixel_format = PixelFormat::D16_UNORM;
89             break;
90         case PixelFormat::R32_FLOAT:
91             params.pixel_format = PixelFormat::D32_FLOAT;
92             break;
93         default:
94             UNIMPLEMENTED_MSG("Unimplemented shadow convert format: {}",
95                               static_cast<u32>(params.pixel_format));
96         }
97         params.type = GetFormatType(params.pixel_format);
98     }
99     // TODO: on 1DBuffer we should use the tic info.
100     if (tic.IsBuffer()) {
101         params.target = SurfaceTarget::TextureBuffer;
102         params.width = tic.Width();
103         params.pitch = params.width * params.GetBytesPerPixel();
104         params.height = 1;
105         params.depth = 1;
106         params.num_levels = 1;
107         params.emulated_levels = 1;
108         params.is_layered = false;
109     } else {
110         params.target = TextureTypeToSurfaceTarget(entry.type, entry.is_array);
111         params.width = tic.Width();
112         params.height = tic.Height();
113         params.depth = tic.Depth();
114         params.pitch = params.is_tiled ? 0 : tic.Pitch();
115         if (params.target == SurfaceTarget::TextureCubemap ||
116             params.target == SurfaceTarget::TextureCubeArray) {
117             params.depth *= 6;
118         }
119         params.num_levels = tic.max_mip_level + 1;
120         params.emulated_levels = std::min(params.num_levels, params.MaxPossibleMipmap());
121         params.is_layered = params.IsLayered();
122     }
123     return params;
124 }
125 
CreateForImage(const FormatLookupTable & lookup_table,const Tegra::Texture::TICEntry & tic,const VideoCommon::Shader::Image & entry)126 SurfaceParams SurfaceParams::CreateForImage(const FormatLookupTable& lookup_table,
127                                             const Tegra::Texture::TICEntry& tic,
128                                             const VideoCommon::Shader::Image& entry) {
129     SurfaceParams params;
130     params.is_tiled = tic.IsTiled();
131     params.srgb_conversion = tic.IsSrgbConversionEnabled();
132     params.block_width = params.is_tiled ? tic.BlockWidth() : 0;
133     params.block_height = params.is_tiled ? tic.BlockHeight() : 0;
134     params.block_depth = params.is_tiled ? tic.BlockDepth() : 0;
135     params.tile_width_spacing = params.is_tiled ? (1 << tic.tile_width_spacing.Value()) : 1;
136     params.pixel_format = lookup_table.GetPixelFormat(
137         tic.format, params.srgb_conversion, tic.r_type, tic.g_type, tic.b_type, tic.a_type);
138     params.type = GetFormatType(params.pixel_format);
139     params.target = ImageTypeToSurfaceTarget(entry.type);
140     // TODO: on 1DBuffer we should use the tic info.
141     if (tic.IsBuffer()) {
142         params.target = SurfaceTarget::TextureBuffer;
143         params.width = tic.Width();
144         params.pitch = params.width * params.GetBytesPerPixel();
145         params.height = 1;
146         params.depth = 1;
147         params.num_levels = 1;
148         params.emulated_levels = 1;
149         params.is_layered = false;
150     } else {
151         params.width = tic.Width();
152         params.height = tic.Height();
153         params.depth = tic.Depth();
154         params.pitch = params.is_tiled ? 0 : tic.Pitch();
155         if (params.target == SurfaceTarget::TextureCubemap ||
156             params.target == SurfaceTarget::TextureCubeArray) {
157             params.depth *= 6;
158         }
159         params.num_levels = tic.max_mip_level + 1;
160         params.emulated_levels = std::min(params.num_levels, params.MaxPossibleMipmap());
161         params.is_layered = params.IsLayered();
162     }
163     return params;
164 }
165 
CreateForDepthBuffer(Tegra::Engines::Maxwell3D & maxwell3d)166 SurfaceParams SurfaceParams::CreateForDepthBuffer(Tegra::Engines::Maxwell3D& maxwell3d) {
167     const auto& regs = maxwell3d.regs;
168     const auto block_depth = std::min(regs.zeta.memory_layout.block_depth.Value(), 5U);
169     const bool is_layered = regs.zeta_layers > 1 && block_depth == 0;
170     const auto pixel_format = PixelFormatFromDepthFormat(regs.zeta.format);
171     return {
172         .is_tiled = regs.zeta.memory_layout.type ==
173                     Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear,
174         .srgb_conversion = false,
175         .is_layered = is_layered,
176         .block_width = std::min(regs.zeta.memory_layout.block_width.Value(), 5U),
177         .block_height = std::min(regs.zeta.memory_layout.block_height.Value(), 5U),
178         .block_depth = block_depth,
179         .tile_width_spacing = 1,
180         .width = regs.zeta_width,
181         .height = regs.zeta_height,
182         .depth = is_layered ? regs.zeta_layers.Value() : 1U,
183         .pitch = 0,
184         .num_levels = 1,
185         .emulated_levels = 1,
186         .pixel_format = pixel_format,
187         .type = GetFormatType(pixel_format),
188         .target = is_layered ? SurfaceTarget::Texture2DArray : SurfaceTarget::Texture2D,
189     };
190 }
191 
CreateForFramebuffer(Tegra::Engines::Maxwell3D & maxwell3d,std::size_t index)192 SurfaceParams SurfaceParams::CreateForFramebuffer(Tegra::Engines::Maxwell3D& maxwell3d,
193                                                   std::size_t index) {
194     const auto& config{maxwell3d.regs.rt[index]};
195     SurfaceParams params;
196     params.is_tiled =
197         config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear;
198     params.srgb_conversion = config.format == Tegra::RenderTargetFormat::B8G8R8A8_SRGB ||
199                              config.format == Tegra::RenderTargetFormat::A8B8G8R8_SRGB;
200     params.block_width = config.memory_layout.block_width;
201     params.block_height = config.memory_layout.block_height;
202     params.block_depth = config.memory_layout.block_depth;
203     params.tile_width_spacing = 1;
204     params.pixel_format = PixelFormatFromRenderTargetFormat(config.format);
205     params.type = GetFormatType(params.pixel_format);
206     if (params.is_tiled) {
207         params.pitch = 0;
208         params.width = config.width;
209     } else {
210         const u32 bpp = GetFormatBpp(params.pixel_format) / CHAR_BIT;
211         params.pitch = config.width;
212         params.width = params.pitch / bpp;
213     }
214     params.height = config.height;
215     params.num_levels = 1;
216     params.emulated_levels = 1;
217 
218     if (config.memory_layout.is_3d != 0) {
219         params.depth = config.layers.Value();
220         params.is_layered = false;
221         params.target = SurfaceTarget::Texture3D;
222     } else if (config.layers > 1) {
223         params.depth = config.layers.Value();
224         params.is_layered = true;
225         params.target = SurfaceTarget::Texture2DArray;
226     } else {
227         params.depth = 1;
228         params.is_layered = false;
229         params.target = SurfaceTarget::Texture2D;
230     }
231     return params;
232 }
233 
CreateForFermiCopySurface(const Tegra::Engines::Fermi2D::Regs::Surface & config)234 SurfaceParams SurfaceParams::CreateForFermiCopySurface(
235     const Tegra::Engines::Fermi2D::Regs::Surface& config) {
236     const bool is_tiled = !config.linear;
237     const auto pixel_format = PixelFormatFromRenderTargetFormat(config.format);
238 
239     SurfaceParams params{
240         .is_tiled = is_tiled,
241         .srgb_conversion = config.format == Tegra::RenderTargetFormat::B8G8R8A8_SRGB ||
242                            config.format == Tegra::RenderTargetFormat::A8B8G8R8_SRGB,
243         .is_layered = false,
244         .block_width = is_tiled ? std::min(config.BlockWidth(), 5U) : 0U,
245         .block_height = is_tiled ? std::min(config.BlockHeight(), 5U) : 0U,
246         .block_depth = is_tiled ? std::min(config.BlockDepth(), 5U) : 0U,
247         .tile_width_spacing = 1,
248         .width = config.width,
249         .height = config.height,
250         .depth = 1,
251         .pitch = config.pitch,
252         .num_levels = 1,
253         .emulated_levels = 1,
254         .pixel_format = pixel_format,
255         .type = GetFormatType(pixel_format),
256         // TODO(Rodrigo): Try to guess texture arrays from parameters
257         .target = SurfaceTarget::Texture2D,
258     };
259 
260     params.is_layered = params.IsLayered();
261     return params;
262 }
263 
ExpectedTarget(const VideoCommon::Shader::Sampler & entry)264 VideoCore::Surface::SurfaceTarget SurfaceParams::ExpectedTarget(
265     const VideoCommon::Shader::Sampler& entry) {
266     return TextureTypeToSurfaceTarget(entry.type, entry.is_array);
267 }
268 
ExpectedTarget(const VideoCommon::Shader::Image & entry)269 VideoCore::Surface::SurfaceTarget SurfaceParams::ExpectedTarget(
270     const VideoCommon::Shader::Image& entry) {
271     return ImageTypeToSurfaceTarget(entry.type);
272 }
273 
IsLayered() const274 bool SurfaceParams::IsLayered() const {
275     switch (target) {
276     case SurfaceTarget::Texture1DArray:
277     case SurfaceTarget::Texture2DArray:
278     case SurfaceTarget::TextureCubemap:
279     case SurfaceTarget::TextureCubeArray:
280         return true;
281     default:
282         return false;
283     }
284 }
285 
286 // Auto block resizing algorithm from:
287 // https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nv50/nv50_miptree.c
GetMipBlockHeight(u32 level) const288 u32 SurfaceParams::GetMipBlockHeight(u32 level) const {
289     if (level == 0) {
290         return this->block_height;
291     }
292 
293     const u32 height_new{GetMipHeight(level)};
294     const u32 default_block_height{GetDefaultBlockHeight()};
295     const u32 blocks_in_y{(height_new + default_block_height - 1) / default_block_height};
296     const u32 block_height_new = Common::Log2Ceil32(blocks_in_y);
297     return std::clamp(block_height_new, 3U, 7U) - 3U;
298 }
299 
GetMipBlockDepth(u32 level) const300 u32 SurfaceParams::GetMipBlockDepth(u32 level) const {
301     if (level == 0) {
302         return this->block_depth;
303     }
304     if (is_layered) {
305         return 0;
306     }
307 
308     const u32 depth_new{GetMipDepth(level)};
309     const u32 block_depth_new = Common::Log2Ceil32(depth_new);
310     if (block_depth_new > 4) {
311         return 5 - (GetMipBlockHeight(level) >= 2);
312     }
313     return block_depth_new;
314 }
315 
GetGuestMipmapLevelOffset(u32 level) const316 std::size_t SurfaceParams::GetGuestMipmapLevelOffset(u32 level) const {
317     std::size_t offset = 0;
318     for (u32 i = 0; i < level; i++) {
319         offset += GetInnerMipmapMemorySize(i, false, false);
320     }
321     return offset;
322 }
323 
GetHostMipmapLevelOffset(u32 level,bool is_converted) const324 std::size_t SurfaceParams::GetHostMipmapLevelOffset(u32 level, bool is_converted) const {
325     std::size_t offset = 0;
326     if (is_converted) {
327         for (u32 i = 0; i < level; ++i) {
328             offset += GetConvertedMipmapSize(i) * GetNumLayers();
329         }
330     } else {
331         for (u32 i = 0; i < level; ++i) {
332             offset += GetInnerMipmapMemorySize(i, true, false) * GetNumLayers();
333         }
334     }
335     return offset;
336 }
337 
GetConvertedMipmapSize(u32 level) const338 std::size_t SurfaceParams::GetConvertedMipmapSize(u32 level) const {
339     constexpr std::size_t rgba8_bpp = 4ULL;
340     const std::size_t mip_width = GetMipWidth(level);
341     const std::size_t mip_height = GetMipHeight(level);
342     const std::size_t mip_depth = is_layered ? 1 : GetMipDepth(level);
343     return mip_width * mip_height * mip_depth * rgba8_bpp;
344 }
345 
GetLayerSize(bool as_host_size,bool uncompressed) const346 std::size_t SurfaceParams::GetLayerSize(bool as_host_size, bool uncompressed) const {
347     std::size_t size = 0;
348     for (u32 level = 0; level < num_levels; ++level) {
349         size += GetInnerMipmapMemorySize(level, as_host_size, uncompressed);
350     }
351     if (is_tiled && is_layered) {
352         return Common::AlignBits(size, Tegra::Texture::GOB_SIZE_SHIFT + block_height + block_depth);
353     }
354     return size;
355 }
356 
GetInnerMipmapMemorySize(u32 level,bool as_host_size,bool uncompressed) const357 std::size_t SurfaceParams::GetInnerMipmapMemorySize(u32 level, bool as_host_size,
358                                                     bool uncompressed) const {
359     const u32 mip_width{GetMipmapSize(uncompressed, GetMipWidth(level), GetDefaultBlockWidth())};
360     const u32 mip_height{GetMipmapSize(uncompressed, GetMipHeight(level), GetDefaultBlockHeight())};
361     const u32 mip_depth{is_layered ? 1U : GetMipDepth(level)};
362     if (is_tiled) {
363         return Tegra::Texture::CalculateSize(!as_host_size, GetBytesPerPixel(), mip_width,
364                                              mip_height, mip_depth, GetMipBlockHeight(level),
365                                              GetMipBlockDepth(level));
366     } else if (as_host_size || IsBuffer()) {
367         return GetBytesPerPixel() * mip_width * mip_height * mip_depth;
368     } else {
369         // Linear Texture Case
370         return pitch * mip_height * mip_depth;
371     }
372 }
373 
operator ==(const SurfaceParams & rhs) const374 bool SurfaceParams::operator==(const SurfaceParams& rhs) const {
375     return std::tie(is_tiled, block_width, block_height, block_depth, tile_width_spacing, width,
376                     height, depth, pitch, num_levels, pixel_format, type, target) ==
377            std::tie(rhs.is_tiled, rhs.block_width, rhs.block_height, rhs.block_depth,
378                     rhs.tile_width_spacing, rhs.width, rhs.height, rhs.depth, rhs.pitch,
379                     rhs.num_levels, rhs.pixel_format, rhs.type, rhs.target);
380 }
381 
TargetName() const382 std::string SurfaceParams::TargetName() const {
383     switch (target) {
384     case SurfaceTarget::Texture1D:
385         return "1D";
386     case SurfaceTarget::TextureBuffer:
387         return "TexBuffer";
388     case SurfaceTarget::Texture2D:
389         return "2D";
390     case SurfaceTarget::Texture3D:
391         return "3D";
392     case SurfaceTarget::Texture1DArray:
393         return "1DArray";
394     case SurfaceTarget::Texture2DArray:
395         return "2DArray";
396     case SurfaceTarget::TextureCubemap:
397         return "Cube";
398     case SurfaceTarget::TextureCubeArray:
399         return "CubeArray";
400     default:
401         LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", target);
402         UNREACHABLE();
403         return fmt::format("TUK({})", target);
404     }
405 }
406 
GetBlockSize() const407 u32 SurfaceParams::GetBlockSize() const {
408     const u32 x = 64U << block_width;
409     const u32 y = 8U << block_height;
410     const u32 z = 1U << block_depth;
411     return x * y * z;
412 }
413 
GetBlockXY() const414 std::pair<u32, u32> SurfaceParams::GetBlockXY() const {
415     const u32 x_pixels = 64U / GetBytesPerPixel();
416     const u32 x = x_pixels << block_width;
417     const u32 y = 8U << block_height;
418     return {x, y};
419 }
420 
GetBlockOffsetXYZ(u32 offset) const421 std::tuple<u32, u32, u32> SurfaceParams::GetBlockOffsetXYZ(u32 offset) const {
422     const auto div_ceil = [](const u32 x, const u32 y) { return ((x + y - 1) / y); };
423     const u32 block_size = GetBlockSize();
424     const u32 block_index = offset / block_size;
425     const u32 gob_offset = offset % block_size;
426     const u32 gob_index = gob_offset / static_cast<u32>(Tegra::Texture::GOB_SIZE);
427     const u32 x_gob_pixels = 64U / GetBytesPerPixel();
428     const u32 x_block_pixels = x_gob_pixels << block_width;
429     const u32 y_block_pixels = 8U << block_height;
430     const u32 z_block_pixels = 1U << block_depth;
431     const u32 x_blocks = div_ceil(width, x_block_pixels);
432     const u32 y_blocks = div_ceil(height, y_block_pixels);
433     const u32 z_blocks = div_ceil(depth, z_block_pixels);
434     const u32 base_x = block_index % x_blocks;
435     const u32 base_y = (block_index / x_blocks) % y_blocks;
436     const u32 base_z = (block_index / (x_blocks * y_blocks)) % z_blocks;
437     u32 x = base_x * x_block_pixels;
438     u32 y = base_y * y_block_pixels;
439     u32 z = base_z * z_block_pixels;
440     z += gob_index >> block_height;
441     y += (gob_index * 8U) % y_block_pixels;
442     return {x, y, z};
443 }
444 
445 } // namespace VideoCommon
446