mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-07 08:36:02 -10:00
strlcpy() begins with strlen() of its source. It lives in another translation unit, so with no LTO that scan happens at runtime even when the source is a literal whose length the compiler already measured, and the call itself cannot be folded away either. strlcpy_lit() passes the length over as sizeof(). When the destination size is also a constant -- sizeof(buf), the usual case -- nothing is left but the stores memcpy() compiles to: strlcpy "core_options" sizeof(buf) 7.01 ns strlcpy_lit "core_options" sizeof(buf) 0.42 ns strlcpy 41-char literal sizeof(buf) 7.74 ns strlcpy_lit 41-char literal sizeof(buf) 0.71 ns strlcpy "core_options" runtime len 7.11 ns strlcpy_lit "core_options" runtime len 0.70 ns The destination bound is kept. When the literal does not fit it defers to strlcpy(), so the copy truncates as before rather than overrunning; a form that took only the literal would have turned every one of these sites into an overflow waiting for a small buffer. The literal is concatenated with "" so a non-literal source is a compile error rather than a sizeof() that silently measures a pointer. 818 sites across 104 files were converted by parsing arguments rather than by pattern-matching text: only a three-argument call whose second argument is a bare string literal is rewritten, so a macro that happens to expand to a literal is left alone. Line endings are preserved, so the CRLF files carry only their own changed lines. test/compat/test_strl.c compares the two against each other over every destination size from 0 past the literal's length, for literals that are empty, single-character, path-shaped, adjacently concatenated and escape-bearing, checking the return, the terminator and the whole buffer so an overrun past the terminator is caught. ASan+UBSan+LSan and TSan both 4/4. The full menu_displaylist_build_list walk over every displaylist type is byte-identical to before the change (md5 a2a081bcac09cdc8d72d67efd14ac40e).
8418 lines
289 KiB
C
8418 lines
289 KiB
C
/* RetroArch - A frontend for libretro.
|
|
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
|
|
* Copyright (C) 2011-2017 - Daniel De Matteis
|
|
* Copyright (C) 2012-2014 - OV2
|
|
*
|
|
* RetroArch 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 Found-
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* RetroArch 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 RetroArch.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/* Direct3D 9 driver with HLSL runtime backend.
|
|
*
|
|
* Minimum version : Direct3D 9.0 (2002)
|
|
* Minimum OS : Windows 98, Windows 2000, Windows ME
|
|
* Recommended OS : Windows XP
|
|
* Requirements : HLSL or fixed function backend
|
|
*/
|
|
|
|
#define CINTERFACE
|
|
|
|
#ifdef _XBOX
|
|
#include <xtl.h>
|
|
#include <xgraphics.h>
|
|
#endif
|
|
|
|
#include <formats/image.h>
|
|
#include <compat/strl.h>
|
|
#include <compat/posix_string.h>
|
|
#include <file/file_path.h>
|
|
#include <streams/file_stream.h>
|
|
#include <string/stdstring.h>
|
|
#include <retro_math.h>
|
|
#include <encodings/utf.h>
|
|
|
|
#include <string.h>
|
|
#include <retro_inline.h>
|
|
#include <retro_math.h>
|
|
#include <math.h>
|
|
|
|
#ifndef _XBOX
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#endif
|
|
#include <d3d9.h>
|
|
#include "../common/d3dcompiler_common.h"
|
|
#include "../common/d3d9_common.h"
|
|
|
|
#include "../video_driver.h"
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "../../config.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_THREADS
|
|
#include "../video_thread_wrapper.h"
|
|
#endif
|
|
|
|
#include <defines/d3d_defines.h>
|
|
#include <gfx/math/matrix_4x4.h>
|
|
#include "../common/d3d_common.h"
|
|
|
|
#include "../../configuration.h"
|
|
#include "../../dynamic.h"
|
|
#include "../../frontend/frontend_driver.h"
|
|
|
|
#include "../common/win32_common.h"
|
|
|
|
#ifdef HAVE_MENU
|
|
#include "../../menu/menu_driver.h"
|
|
#endif
|
|
#ifdef HAVE_GFX_WIDGETS
|
|
#include "../gfx_widgets.h"
|
|
#endif
|
|
|
|
#include "../font_driver.h"
|
|
|
|
#include "../../core.h"
|
|
#include "../../verbosity.h"
|
|
#include "../../retroarch.h"
|
|
|
|
#include "d3d_shaders/shaders_common.h"
|
|
#include "d3d_shaders/ribbon_sm3.hlsl.h"
|
|
#include "d3d_shaders/ribbon_simple_sm3.hlsl.h"
|
|
#include "d3d_shaders/simple_snow_sm3.hlsl.h"
|
|
#include "d3d_shaders/snow_sm3.hlsl.h"
|
|
#include "d3d_shaders/bokeh_sm3.hlsl.h"
|
|
#include "d3d_shaders/snowflake_sm3.hlsl.h"
|
|
|
|
static const char *stock_hlsl_program = CG(
|
|
void main_vertex
|
|
(
|
|
float3 position : POSITION,
|
|
float4 color : COLOR,
|
|
float2 texCoord : TEXCOORD0,
|
|
|
|
uniform float4x4 modelViewProj,
|
|
|
|
out float4 oPosition : POSITION,
|
|
out float4 oColor : COLOR,
|
|
out float2 otexCoord : TEXCOORD
|
|
)
|
|
{
|
|
oPosition = mul(modelViewProj, float4(position, 1.0f));
|
|
oColor = color;
|
|
otexCoord = texCoord;
|
|
}
|
|
|
|
struct output
|
|
{
|
|
float4 color: COLOR;
|
|
};
|
|
|
|
struct input
|
|
{
|
|
float2 video_size;
|
|
float2 texture_size;
|
|
float2 output_size;
|
|
float frame_count;
|
|
float frame_direction;
|
|
float frame_rotation;
|
|
};
|
|
|
|
output main_fragment(float4 color : COLOR, float2 texCoord : TEXCOORD0,
|
|
uniform sampler2D decal : TEXUNIT0, uniform input IN)
|
|
{
|
|
output OUT;
|
|
OUT.color = color * tex2D(decal, texCoord);
|
|
return OUT;
|
|
}
|
|
);
|
|
|
|
#ifdef __WINRT__
|
|
#error "UWP does not support D3D9"
|
|
#endif
|
|
|
|
#define D3D_FILTER_LINEAR (3 << 0)
|
|
#define D3D_FILTER_POINT (2 << 0)
|
|
|
|
struct lut_info
|
|
{
|
|
LPDIRECT3DTEXTURE9 tex;
|
|
char id[64];
|
|
bool smooth;
|
|
};
|
|
|
|
struct shader_pass
|
|
{
|
|
unsigned last_width, last_height;
|
|
struct LinkInfo info;
|
|
D3DPOOL pool;
|
|
LPDIRECT3DTEXTURE9 tex;
|
|
LPDIRECT3DVERTEXBUFFER9 vertex_buf;
|
|
LPDIRECT3DVERTEXDECLARATION9 vertex_decl;
|
|
void *attrib_map;
|
|
void *vprg;
|
|
void *fprg;
|
|
void *vtable;
|
|
void *ftable;
|
|
};
|
|
|
|
#define D3D_PI 3.14159265358979323846264338327
|
|
|
|
#define VECTOR_LIST_TYPE unsigned
|
|
#define VECTOR_LIST_NAME unsigned
|
|
#include "../../libretro-common/lists/vector_list.c"
|
|
#undef VECTOR_LIST_TYPE
|
|
#undef VECTOR_LIST_NAME
|
|
|
|
#define VECTOR_LIST_TYPE struct lut_info
|
|
#define VECTOR_LIST_NAME lut_info
|
|
#include "../../libretro-common/lists/vector_list.c"
|
|
#undef VECTOR_LIST_TYPE
|
|
#undef VECTOR_LIST_NAME
|
|
|
|
#define VECTOR_LIST_TYPE struct shader_pass
|
|
#define VECTOR_LIST_NAME shader_pass
|
|
#include "../../libretro-common/lists/vector_list.c"
|
|
#undef VECTOR_LIST_TYPE
|
|
#undef VECTOR_LIST_NAME
|
|
|
|
typedef struct d3d9_hlsl_renderchain
|
|
{
|
|
unsigned pixel_size;
|
|
uint64_t frame_count;
|
|
struct
|
|
{
|
|
LPDIRECT3DTEXTURE9 tex[TEXTURES];
|
|
LPDIRECT3DVERTEXBUFFER9 vertex_buf[TEXTURES];
|
|
unsigned ptr;
|
|
unsigned last_width[TEXTURES];
|
|
unsigned last_height[TEXTURES];
|
|
} prev;
|
|
LPDIRECT3DDEVICE9 dev;
|
|
D3DVIEWPORT9 *out_vp;
|
|
struct shader_pass_vector_list *passes;
|
|
struct unsigned_vector_list *bound_tex;
|
|
struct unsigned_vector_list *bound_vert;
|
|
struct lut_info_vector_list *luts;
|
|
} d3d9_hlsl_renderchain_t;
|
|
|
|
static INLINE bool d3d9_hlsl_renderchain_add_lut(d3d9_hlsl_renderchain_t *chain,
|
|
const char *id, const char *path, bool smooth)
|
|
{
|
|
struct lut_info info;
|
|
struct texture_image image;
|
|
LPDIRECT3DTEXTURE9 lut = NULL;
|
|
|
|
image.pixels = NULL;
|
|
image.width = 0;
|
|
image.height = 0;
|
|
image.supports_rgba = true;
|
|
|
|
if (!image_texture_load(&image, path))
|
|
{
|
|
RARCH_ERR("[D3D9] Failed to load LUT image: %s.\n", path);
|
|
return false;
|
|
}
|
|
|
|
lut = NULL;
|
|
IDirect3DDevice9_CreateTexture(chain->dev,
|
|
image.width, image.height, 1,
|
|
0, D3D9_ARGB8888_FORMAT,
|
|
D3DPOOL_MANAGED,
|
|
(struct IDirect3DTexture9**)&lut, NULL);
|
|
|
|
if (!lut)
|
|
{
|
|
image_texture_free(&image);
|
|
return false;
|
|
}
|
|
|
|
{
|
|
D3DLOCKED_RECT lr;
|
|
if (SUCCEEDED(IDirect3DTexture9_LockRect(lut, 0, &lr, NULL, 0)))
|
|
{
|
|
unsigned y;
|
|
uint32_t *dst = (uint32_t*)lr.pBits;
|
|
const uint32_t *src = image.pixels;
|
|
unsigned pitch = lr.Pitch >> 2;
|
|
for (y = 0; y < image.height; y++, dst += pitch, src += image.width)
|
|
memcpy(dst, src, image.width << 2);
|
|
IDirect3DTexture9_UnlockRect(lut, 0);
|
|
}
|
|
}
|
|
|
|
image_texture_free(&image);
|
|
|
|
RARCH_LOG("[D3D9] LUT texture loaded: %s.\n", path);
|
|
|
|
info.tex = lut;
|
|
info.smooth = smooth;
|
|
strlcpy(info.id, id, sizeof(info.id));
|
|
|
|
IDirect3DDevice9_SetTexture(chain->dev, 0, (IDirect3DBaseTexture9*)lut);
|
|
IDirect3DDevice9_SetSamplerState(chain->dev, 0, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER);
|
|
IDirect3DDevice9_SetSamplerState(chain->dev, 0, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER);
|
|
IDirect3DDevice9_SetTexture(chain->dev, 0, (IDirect3DBaseTexture9*)NULL);
|
|
|
|
lut_info_vector_list_append(chain->luts, info);
|
|
|
|
return true;
|
|
}
|
|
|
|
static INLINE bool d3d9_hlsl_renderchain_set_pass_size(
|
|
LPDIRECT3DDEVICE9 dev,
|
|
struct shader_pass *pass,
|
|
struct shader_pass *pass2,
|
|
unsigned width, unsigned height)
|
|
{
|
|
if (width != pass->info.tex_w || height != pass->info.tex_h)
|
|
{
|
|
IDirect3DTexture9_Release(pass->tex);
|
|
|
|
pass->info.tex_w = width;
|
|
pass->info.tex_h = height;
|
|
pass->pool = D3DPOOL_DEFAULT;
|
|
pass->tex = NULL;
|
|
IDirect3DDevice9_CreateTexture(dev,
|
|
width, height, 1,
|
|
D3DUSAGE_RENDERTARGET,
|
|
(pass2->info.pass->fbo.flags & FBO_SCALE_FLAG_FP_FBO)
|
|
? D3DFMT_A32B32G32R32F
|
|
: D3D9_ARGB8888_FORMAT,
|
|
D3DPOOL_DEFAULT,
|
|
(struct IDirect3DTexture9**)&pass->tex, NULL);
|
|
|
|
if (!pass->tex)
|
|
return false;
|
|
|
|
IDirect3DDevice9_SetTexture(dev, 0, (IDirect3DBaseTexture9*)pass->tex);
|
|
IDirect3DDevice9_SetSamplerState(dev, 0, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER);
|
|
IDirect3DDevice9_SetSamplerState(dev, 0, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER);
|
|
IDirect3DDevice9_SetTexture(dev, 0, (IDirect3DBaseTexture9*)NULL);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static INLINE void d3d9_recompute_pass_sizes(
|
|
LPDIRECT3DDEVICE9 dev,
|
|
d3d9_hlsl_renderchain_t *chain,
|
|
d3d9_video_t *d3d)
|
|
{
|
|
int i;
|
|
struct LinkInfo link_info;
|
|
unsigned input_scale = d3d->video_info.input_scale
|
|
* RARCH_SCALE_BASE;
|
|
unsigned current_width = input_scale;
|
|
unsigned current_height = input_scale;
|
|
unsigned out_width = 0;
|
|
unsigned out_height = 0;
|
|
|
|
link_info.pass = &d3d->shader.pass[0];
|
|
link_info.tex_w = current_width;
|
|
link_info.tex_h = current_height;
|
|
|
|
if (!d3d9_hlsl_renderchain_set_pass_size(dev,
|
|
(struct shader_pass*)&chain->passes->data[0],
|
|
(struct shader_pass*)&chain->passes->data[
|
|
chain->passes->count - 1],
|
|
current_width, current_height))
|
|
{
|
|
RARCH_ERR("[D3D9] Failed to set pass size.\n");
|
|
return;
|
|
}
|
|
|
|
for (i = 1; i < (int) d3d->shader.passes; i++)
|
|
{
|
|
switch (link_info.pass->fbo.type_x)
|
|
{
|
|
case RARCH_SCALE_VIEWPORT:
|
|
out_width = link_info.pass->fbo.scale_x * d3d->out_vp.Width;
|
|
break;
|
|
case RARCH_SCALE_ABSOLUTE:
|
|
out_width = link_info.pass->fbo.abs_x;
|
|
break;
|
|
case RARCH_SCALE_INPUT:
|
|
out_width = link_info.pass->fbo.scale_x * current_width;
|
|
break;
|
|
}
|
|
switch (link_info.pass->fbo.type_y)
|
|
{
|
|
case RARCH_SCALE_VIEWPORT:
|
|
out_height = link_info.pass->fbo.scale_y * d3d->out_vp.Height;
|
|
break;
|
|
case RARCH_SCALE_ABSOLUTE:
|
|
out_height = link_info.pass->fbo.abs_y;
|
|
break;
|
|
case RARCH_SCALE_INPUT:
|
|
out_height = link_info.pass->fbo.scale_y * current_height;
|
|
break;
|
|
}
|
|
|
|
link_info.tex_w = next_pow2(out_width);
|
|
link_info.tex_h = next_pow2(out_height);
|
|
|
|
if (!d3d9_hlsl_renderchain_set_pass_size(dev,
|
|
(struct shader_pass*)&chain->passes->data[i],
|
|
(struct shader_pass*)&chain->passes->data[
|
|
chain->passes->count - 1],
|
|
link_info.tex_w, link_info.tex_h))
|
|
{
|
|
RARCH_ERR("[D3D9] Failed to set pass size.\n");
|
|
return;
|
|
}
|
|
|
|
current_width = out_width;
|
|
current_height = out_height;
|
|
|
|
link_info.pass = &d3d->shader.pass[i];
|
|
}
|
|
}
|
|
|
|
/* =====================================================================
|
|
* SM3.0 CTAB (Constant Table) parser
|
|
*
|
|
* Parses the CTAB embedded in compiled SM3.0 shader bytecode to find
|
|
* hardware register indices by uniform name.
|
|
* ===================================================================== */
|
|
|
|
#define CTAB_REGSET_FLOAT4 2
|
|
#define CTAB_REGSET_SAMPLER 3
|
|
#define CTAB_NOT_FOUND ((int)-1)
|
|
|
|
static int d3d9_hlsl_ctab_find_register(
|
|
const DWORD *bytecode, size_t bytecode_dwords,
|
|
const char *name, int *out_regset, int *out_count)
|
|
{
|
|
size_t pos;
|
|
const DWORD CTAB_TAG = 0x42415443; /* 'CTAB' */
|
|
|
|
if (out_regset)
|
|
*out_regset = -1;
|
|
if (out_count)
|
|
*out_count = 0;
|
|
if (!bytecode || bytecode_dwords < 2 || !name || !*name)
|
|
return CTAB_NOT_FOUND;
|
|
|
|
for (pos = 1; pos < bytecode_dwords; )
|
|
{
|
|
DWORD token = bytecode[pos];
|
|
DWORD opcode = token & 0xFFFF;
|
|
|
|
if (opcode == 0xFFFF)
|
|
break;
|
|
|
|
if (opcode == 0xFFFE)
|
|
{
|
|
DWORD comment_dwords = (token >> 16) & 0x7FFF;
|
|
size_t comment_bytes;
|
|
|
|
if (pos + 1 + comment_dwords > bytecode_dwords)
|
|
break;
|
|
|
|
comment_bytes = comment_dwords * sizeof(DWORD);
|
|
|
|
if (comment_dwords >= 6 && bytecode[pos + 1] == CTAB_TAG)
|
|
{
|
|
const uint8_t *ctab_start = (const uint8_t*)&bytecode[pos + 1];
|
|
size_t ctab_total_bytes = comment_bytes;
|
|
const uint8_t *hdr = ctab_start + 4;
|
|
size_t hdr_avail = ctab_total_bytes - 4;
|
|
DWORD num_constants;
|
|
DWORD const_info_offset;
|
|
DWORD ci;
|
|
|
|
if (hdr_avail < 28)
|
|
return CTAB_NOT_FOUND;
|
|
|
|
num_constants = *(const DWORD*)(hdr + 12);
|
|
const_info_offset = *(const DWORD*)(hdr + 16);
|
|
|
|
for (ci = 0; ci < num_constants; ci++)
|
|
{
|
|
size_t entry_off = const_info_offset + ci * 20;
|
|
const uint8_t *entry;
|
|
DWORD name_offset;
|
|
WORD reg_set, reg_index;
|
|
const char *cname;
|
|
|
|
if (entry_off + 20 > hdr_avail)
|
|
break;
|
|
|
|
entry = hdr + entry_off;
|
|
name_offset = *(const DWORD*)(entry + 0);
|
|
reg_set = *(const WORD*)(entry + 4);
|
|
reg_index = *(const WORD*)(entry + 6);
|
|
|
|
if (name_offset >= hdr_avail)
|
|
continue;
|
|
|
|
cname = (const char*)(hdr + name_offset);
|
|
|
|
/* D3DCompile with backwards compat may prefix names with '$' */
|
|
if (string_is_equal(cname, name)
|
|
|| (cname[0] == '$' && string_is_equal(cname + 1, name)))
|
|
{
|
|
if (out_regset)
|
|
*out_regset = (int)reg_set;
|
|
if (out_count)
|
|
*out_count = (int)*(const WORD*)(entry + 8);
|
|
return (int)reg_index;
|
|
}
|
|
}
|
|
return CTAB_NOT_FOUND;
|
|
}
|
|
|
|
pos += 1 + comment_dwords;
|
|
continue;
|
|
}
|
|
|
|
pos++;
|
|
}
|
|
|
|
return CTAB_NOT_FOUND;
|
|
}
|
|
|
|
|
|
static INLINE void d3d9_hlsl_set_vs_const(LPDIRECT3DDEVICE9 dev,
|
|
int reg, const float *data, unsigned float4_count)
|
|
{
|
|
if (reg >= 0)
|
|
IDirect3DDevice9_SetVertexShaderConstantF(dev, reg, data, float4_count);
|
|
}
|
|
|
|
static INLINE void d3d9_hlsl_set_ps_const(LPDIRECT3DDEVICE9 dev,
|
|
int reg, const float *data, unsigned float4_count)
|
|
{
|
|
if (reg >= 0)
|
|
IDirect3DDevice9_SetPixelShaderConstantF(dev, reg, data, float4_count);
|
|
}
|
|
|
|
typedef struct hlsl_uniform_map
|
|
{
|
|
int mvp;
|
|
int video_size;
|
|
int texture_size;
|
|
int output_size;
|
|
int frame_count;
|
|
int orig_video_size;
|
|
int orig_texture_size;
|
|
int orig_texture;
|
|
int prev_video_size[TEXTURES - 1];
|
|
int prev_texture_size[TEXTURES - 1];
|
|
int prev_texture[TEXTURES - 1];
|
|
int pass_video_size[GFX_MAX_SHADERS];
|
|
int pass_texture_size[GFX_MAX_SHADERS];
|
|
int pass_texture[GFX_MAX_SHADERS];
|
|
} hlsl_uniform_map_t;
|
|
|
|
static void hlsl_uniform_map_init(hlsl_uniform_map_t *map)
|
|
{
|
|
memset(map, 0xFF, sizeof(*map)); /* -1 = not found */
|
|
}
|
|
|
|
static void hlsl_uniform_map_from_bytecode(
|
|
hlsl_uniform_map_t *map,
|
|
const DWORD *bytecode, size_t bytecode_dwords)
|
|
{
|
|
int i;
|
|
char attr[64];
|
|
static const char *prev_names[] = {
|
|
"PREV", "PREV1", "PREV2", "PREV3", "PREV4", "PREV5", "PREV6"
|
|
};
|
|
|
|
hlsl_uniform_map_init(map);
|
|
if (!bytecode)
|
|
return;
|
|
|
|
map->mvp = d3d9_hlsl_ctab_find_register(bytecode, bytecode_dwords, "modelViewProj", NULL, NULL);
|
|
map->video_size = d3d9_hlsl_ctab_find_register(bytecode, bytecode_dwords, "IN.video_size", NULL, NULL);
|
|
map->texture_size = d3d9_hlsl_ctab_find_register(bytecode, bytecode_dwords, "IN.texture_size", NULL, NULL);
|
|
map->output_size = d3d9_hlsl_ctab_find_register(bytecode, bytecode_dwords, "IN.output_size", NULL, NULL);
|
|
map->frame_count = d3d9_hlsl_ctab_find_register(bytecode, bytecode_dwords, "IN.frame_count", NULL, NULL);
|
|
|
|
/* If individual IN.* lookups failed, check if the struct is
|
|
* registered as a single "IN" entry. D3DCompile packs struct
|
|
* members into consecutive float4 registers:
|
|
* c[N+0] = { video_size.x, video_size.y, texture_size.x, texture_size.y }
|
|
* c[N+1] = { output_size.x, output_size.y, frame_count, frame_direction }
|
|
*/
|
|
if (map->video_size < 0 && map->texture_size < 0)
|
|
{
|
|
int in_cnt = 0;
|
|
int in_reg = d3d9_hlsl_ctab_find_register(bytecode, bytecode_dwords, "IN", NULL, &in_cnt);
|
|
if (in_reg >= 0)
|
|
{
|
|
if (in_cnt >= 3)
|
|
{
|
|
/* Per-member packing (cnt >= 3):
|
|
* c[N+0] = { video_size.x, video_size.y, 0, 0 }
|
|
* c[N+1] = { texture_size.x, texture_size.y, 0, 0 }
|
|
* c[N+2] = { output_size.x, output_size.y, 0, 0 }
|
|
* For cnt==4:
|
|
* c[N+3] = { frame_count, frame_direction, 0, 0 }
|
|
* For cnt==3, frame_count is packed into c[N+2].z */
|
|
map->video_size = in_reg;
|
|
map->texture_size = in_reg + 1;
|
|
map->output_size = in_reg + 2;
|
|
map->frame_count = (in_cnt >= 4) ? in_reg + 3 : in_reg + 2;
|
|
}
|
|
else
|
|
{
|
|
/* Tight packing (cnt <= 2):
|
|
* c[N+0] = { video_size.x, video_size.y, texture_size.x, texture_size.y }
|
|
* c[N+1] = { output_size.x, output_size.y, frame_count, frame_direction } */
|
|
map->video_size = in_reg;
|
|
map->texture_size = in_reg;
|
|
map->output_size = in_reg + 1;
|
|
map->frame_count = in_reg + 1;
|
|
}
|
|
}
|
|
}
|
|
map->orig_video_size = d3d9_hlsl_ctab_find_register(bytecode, bytecode_dwords, "ORIG.video_size", NULL, NULL);
|
|
map->orig_texture_size = d3d9_hlsl_ctab_find_register(bytecode, bytecode_dwords, "ORIG.texture_size", NULL, NULL);
|
|
map->orig_texture = d3d9_hlsl_ctab_find_register(bytecode, bytecode_dwords, "ORIG.texture", NULL, NULL);
|
|
/* Fallback: decomposed sampler names (ORIG__texture instead of ORIG.texture) */
|
|
if (map->orig_texture < 0)
|
|
map->orig_texture = d3d9_hlsl_ctab_find_register(bytecode, bytecode_dwords, "ORIG__texture", NULL, NULL);
|
|
|
|
for (i = 0; i < TEXTURES - 1; i++)
|
|
{
|
|
size_t _len = strlcpy(attr, prev_names[i], sizeof(attr));
|
|
strlcpy_lit(attr + _len, ".video_size", sizeof(attr) - _len);
|
|
map->prev_video_size[i] = d3d9_hlsl_ctab_find_register(bytecode, bytecode_dwords, attr, NULL, NULL);
|
|
strlcpy_lit(attr + _len, ".texture_size", sizeof(attr) - _len);
|
|
map->prev_texture_size[i] = d3d9_hlsl_ctab_find_register(bytecode, bytecode_dwords, attr, NULL, NULL);
|
|
strlcpy_lit(attr + _len, ".texture", sizeof(attr) - _len);
|
|
map->prev_texture[i] = d3d9_hlsl_ctab_find_register(bytecode, bytecode_dwords, attr, NULL, NULL);
|
|
/* Fallback: decomposed sampler name */
|
|
if (map->prev_texture[i] < 0)
|
|
{
|
|
strlcpy_lit(attr + _len, "__texture", sizeof(attr) - _len);
|
|
map->prev_texture[i] = d3d9_hlsl_ctab_find_register(bytecode, bytecode_dwords, attr, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < GFX_MAX_SHADERS; i++)
|
|
{
|
|
size_t _len = snprintf(attr, sizeof(attr), "PASS%u", i + 1);
|
|
strlcpy_lit(attr + _len, ".video_size", sizeof(attr) - _len);
|
|
map->pass_video_size[i] = d3d9_hlsl_ctab_find_register(bytecode, bytecode_dwords, attr, NULL, NULL);
|
|
strlcpy_lit(attr + _len, ".texture_size", sizeof(attr) - _len);
|
|
map->pass_texture_size[i] = d3d9_hlsl_ctab_find_register(bytecode, bytecode_dwords, attr, NULL, NULL);
|
|
strlcpy_lit(attr + _len, ".texture", sizeof(attr) - _len);
|
|
map->pass_texture[i] = d3d9_hlsl_ctab_find_register(bytecode, bytecode_dwords, attr, NULL, NULL);
|
|
/* Fallback: decomposed sampler name */
|
|
if (map->pass_texture[i] < 0)
|
|
{
|
|
strlcpy_lit(attr + _len, "__texture", sizeof(attr) - _len);
|
|
map->pass_texture[i] = d3d9_hlsl_ctab_find_register(bytecode, bytecode_dwords, attr, NULL, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef struct hlsl_pass_data
|
|
{
|
|
DWORD *vs_bytecode;
|
|
DWORD *ps_bytecode;
|
|
size_t vs_bytecode_dwords;
|
|
size_t ps_bytecode_dwords;
|
|
hlsl_uniform_map_t vs_map;
|
|
hlsl_uniform_map_t ps_map;
|
|
} hlsl_pass_data_t;
|
|
|
|
static void hlsl_pass_data_free(hlsl_pass_data_t *pd)
|
|
{
|
|
if (pd->vs_bytecode) { free(pd->vs_bytecode); pd->vs_bytecode = NULL; }
|
|
if (pd->ps_bytecode) { free(pd->ps_bytecode); pd->ps_bytecode = NULL; }
|
|
pd->vs_bytecode_dwords = 0;
|
|
pd->ps_bytecode_dwords = 0;
|
|
}
|
|
|
|
/* BEGIN HLSL RENDERCHAIN */
|
|
|
|
#define RARCH_HLSL_MAX_SHADERS 16
|
|
|
|
typedef struct hlsl_renderchain
|
|
{
|
|
struct d3d9_hlsl_renderchain chain;
|
|
struct shader_pass stock_shader;
|
|
/* Per-pass HLSL uniform data for shader presets */
|
|
hlsl_pass_data_t pass_data[GFX_MAX_SHADERS + 1];
|
|
unsigned pass_data_count;
|
|
hlsl_pass_data_t stock_data;
|
|
/* XMB pipeline shaders */
|
|
struct shader_pass pipeline_shaders[6]; /* MENU..MENU_6 */
|
|
hlsl_pass_data_t pipeline_data[6];
|
|
bool pipeline_inited;
|
|
} hlsl_renderchain_t;
|
|
|
|
/* Pipeline vertex buffer for menu shader effects (VIDEO_SHADER_MENU, etc.).
|
|
* Stored as a file-static since the d3d9 menu_display struct
|
|
* does not have a pipeline_vbo member like d3d10_video_t does. */
|
|
static LPDIRECT3DVERTEXBUFFER9 d3d9_hlsl_menu_pipeline_vbo = NULL;
|
|
static LPDIRECT3DVERTEXBUFFER9 d3d9_hlsl_menu_fullscreen_vbo = NULL;
|
|
static LPDIRECT3DVERTEXDECLARATION9 d3d9_hlsl_pipeline_decl = NULL;
|
|
static LPDIRECT3DVERTEXDECLARATION9 d3d9_hlsl_overlay_decl = NULL;
|
|
|
|
static void d3d9_vertex_buffer_free(void *vertex_data, void *vertex_declaration)
|
|
{
|
|
if (vertex_data)
|
|
{
|
|
LPDIRECT3DVERTEXBUFFER9 buf =
|
|
(LPDIRECT3DVERTEXBUFFER9)vertex_data;
|
|
IDirect3DVertexBuffer9_Release(buf);
|
|
buf = NULL;
|
|
}
|
|
|
|
if (vertex_declaration)
|
|
{
|
|
LPDIRECT3DVERTEXDECLARATION9 vertex_decl =
|
|
(LPDIRECT3DVERTEXDECLARATION9)vertex_declaration;
|
|
IDirect3DVertexDeclaration9_Release(vertex_decl);
|
|
vertex_decl = NULL;
|
|
}
|
|
}
|
|
|
|
/* Forward declarations for functions used by display driver
|
|
* but defined in the video driver section below. */
|
|
static INLINE void d3d9_hlsl_set_param_1f(
|
|
const DWORD *bytecode, size_t bytecode_dwords,
|
|
bool is_vertex,
|
|
LPDIRECT3DDEVICE9 dev, const char *name, const void *value);
|
|
|
|
/*
|
|
* DISPLAY DRIVER
|
|
*/
|
|
|
|
static const float d3d9_hlsl_vertexes[8] = {
|
|
0, 0,
|
|
1, 0,
|
|
0, 1,
|
|
1, 1
|
|
};
|
|
|
|
static const float d3d9_hlsl_tex_coords[8] = {
|
|
0, 1,
|
|
1, 1,
|
|
0, 0,
|
|
1, 0
|
|
};
|
|
|
|
static LPDIRECT3DTEXTURE9 d3d9_hlsl_white_texture = NULL;
|
|
|
|
static void gfx_display_d3d9_hlsl_blend_begin(void *data)
|
|
{
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
|
|
if (!d3d)
|
|
return;
|
|
|
|
IDirect3DDevice9_SetRenderState(d3d->dev, D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
|
|
IDirect3DDevice9_SetRenderState(d3d->dev, D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
|
|
IDirect3DDevice9_SetRenderState(d3d->dev, D3DRS_ALPHABLENDENABLE, true);
|
|
}
|
|
|
|
static void gfx_display_d3d9_hlsl_blend_end(void *data)
|
|
{
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
|
|
if (d3d)
|
|
IDirect3DDevice9_SetRenderState(d3d->dev, D3DRS_ALPHABLENDENABLE, false);
|
|
}
|
|
|
|
static void gfx_display_d3d9_bind_texture(gfx_display_ctx_draw_t *draw,
|
|
d3d9_video_t *d3d)
|
|
{
|
|
LPDIRECT3DDEVICE9 dev = d3d->dev;
|
|
|
|
if (draw->texture)
|
|
IDirect3DDevice9_SetTexture(dev, 0,
|
|
(IDirect3DBaseTexture9*)draw->texture);
|
|
else if (d3d9_hlsl_white_texture)
|
|
IDirect3DDevice9_SetTexture(dev, 0,
|
|
(IDirect3DBaseTexture9*)d3d9_hlsl_white_texture);
|
|
else
|
|
IDirect3DDevice9_SetTexture(dev, 0, NULL);
|
|
|
|
IDirect3DDevice9_SetSamplerState(dev,
|
|
0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
|
|
IDirect3DDevice9_SetSamplerState(dev,
|
|
0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
|
|
IDirect3DDevice9_SetSamplerState(dev,
|
|
0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
|
|
IDirect3DDevice9_SetSamplerState(dev,
|
|
0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
|
|
IDirect3DDevice9_SetSamplerState(dev, 0,
|
|
D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
|
|
}
|
|
|
|
static void gfx_display_d3d9_hlsl_draw(gfx_display_ctx_draw_t *draw,
|
|
void *data, unsigned video_width, unsigned video_height)
|
|
{
|
|
unsigned i;
|
|
LPDIRECT3DDEVICE9 dev;
|
|
bool has_vertex_data;
|
|
unsigned start = 0;
|
|
unsigned count = 0;
|
|
unsigned vertex_count = 4;
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
Vertex * pv = NULL;
|
|
const float *vertex = NULL;
|
|
const float *tex_coord = NULL;
|
|
const float *color = NULL;
|
|
|
|
if (!d3d || !draw)
|
|
return;
|
|
|
|
dev = d3d->dev;
|
|
|
|
switch (draw->pipeline_id)
|
|
{
|
|
case VIDEO_SHADER_MENU:
|
|
case VIDEO_SHADER_MENU_2:
|
|
case VIDEO_SHADER_MENU_3:
|
|
case VIDEO_SHADER_MENU_4:
|
|
case VIDEO_SHADER_MENU_5:
|
|
case VIDEO_SHADER_MENU_6:
|
|
{
|
|
hlsl_renderchain_t *_chain = (hlsl_renderchain_t*)d3d->renderchain_data;
|
|
|
|
if (_chain && _chain->pipeline_inited)
|
|
{
|
|
unsigned idx = VIDEO_SHADER_MENU - draw->pipeline_id;
|
|
struct shader_pass *shader = &_chain->pipeline_shaders[idx];
|
|
|
|
IDirect3DDevice9_SetVertexShader(dev,
|
|
(LPDIRECT3DVERTEXSHADER9)shader->vprg);
|
|
IDirect3DDevice9_SetPixelShader(dev,
|
|
(LPDIRECT3DPIXELSHADER9)shader->fprg);
|
|
IDirect3DDevice9_SetVertexDeclaration(dev,
|
|
(LPDIRECT3DVERTEXDECLARATION9)d3d->menu_display.decl);
|
|
|
|
/* Snow/bokeh/snowflake need MVP matrix set */
|
|
if (draw->pipeline_id <= VIDEO_SHADER_MENU_3)
|
|
{
|
|
hlsl_pass_data_t *pd = &_chain->pipeline_data[idx];
|
|
/* Row-major ortho for mul(matrix, vector) in shader.
|
|
* D3D9 SetVertexShaderConstantF stores rows sequentially.
|
|
* mul(M, v) computes dot(row[i], v) — correct with row-major.
|
|
* Maps X [0,1]→[-1,1], Y [0,1]→[-1,1], Z pass-through. */
|
|
static const float ortho_mvp[16] = {
|
|
2.0f, 0.0f, 0.0f, 0.0f,
|
|
0.0f, 2.0f, 0.0f, 0.0f,
|
|
0.0f, 0.0f, 1.0f, 0.0f,
|
|
-1.0f, -1.0f, 0.0f, 1.0f
|
|
};
|
|
{
|
|
int reg = d3d9_hlsl_ctab_find_register(
|
|
pd->vs_bytecode, pd->vs_bytecode_dwords,
|
|
"modelViewProj", NULL, NULL);
|
|
if (reg >= 0)
|
|
d3d9_hlsl_set_vs_const(dev, reg, ortho_mvp, 4);
|
|
}
|
|
}
|
|
|
|
IDirect3DDevice9_DrawPrimitive(dev, D3DPT_TRIANGLESTRIP,
|
|
0, draw->coords->vertices - 2);
|
|
}
|
|
else if (_chain)
|
|
{
|
|
/* Fallback to stock shader */
|
|
IDirect3DDevice9_SetVertexShader(dev,
|
|
(LPDIRECT3DVERTEXSHADER9)(&_chain->stock_shader)->vprg);
|
|
IDirect3DDevice9_SetPixelShader(dev,
|
|
(LPDIRECT3DPIXELSHADER9)(&_chain->stock_shader)->fprg);
|
|
IDirect3DDevice9_DrawPrimitive(dev, D3DPT_TRIANGLESTRIP,
|
|
0, draw->coords->vertices - 2);
|
|
}
|
|
|
|
/* Re-enable alpha blending after pipeline draw */
|
|
IDirect3DDevice9_SetRenderState(dev,
|
|
D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
|
|
IDirect3DDevice9_SetRenderState(dev,
|
|
D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
|
|
IDirect3DDevice9_SetRenderState(dev,
|
|
D3DRS_ALPHABLENDENABLE, true);
|
|
|
|
/* Restore menu display vertex buffer state */
|
|
IDirect3DDevice9_SetStreamSource(dev, 0,
|
|
(LPDIRECT3DVERTEXBUFFER9)d3d->menu_display.buffer,
|
|
0, sizeof(Vertex));
|
|
IDirect3DDevice9_SetVertexDeclaration(dev,
|
|
(LPDIRECT3DVERTEXDECLARATION9)d3d->menu_display.decl);
|
|
|
|
/* Restore stock shader and its constants so subsequent
|
|
* non-pipeline draws (text, icons) work correctly. */
|
|
if (_chain)
|
|
{
|
|
IDirect3DDevice9_SetVertexShader(dev,
|
|
(LPDIRECT3DVERTEXSHADER9)(&_chain->stock_shader)->vprg);
|
|
IDirect3DDevice9_SetPixelShader(dev,
|
|
(LPDIRECT3DPIXELSHADER9)(&_chain->stock_shader)->fprg);
|
|
}
|
|
return;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* Determine whether caller provides explicit vertex arrays or
|
|
* expects us to build the quad from draw->x/y/width/height
|
|
* (the single-sprite path used by Ozone and other modern menus).
|
|
* Mirrors the d3d10 vertex_count==1 vs multi-vertex split. */
|
|
has_vertex_data = draw->coords->vertex
|
|
&& draw->coords->tex_coord && draw->coords->color;
|
|
|
|
if (has_vertex_data)
|
|
vertex_count = draw->coords->vertices;
|
|
|
|
if (!has_vertex_data)
|
|
{
|
|
/* Single-sprite path: no explicit vertex arrays provided.
|
|
* Build a quad directly from draw->x/y/width/height in
|
|
* normalized [0,1] space, using DrawPrimitiveUP to avoid
|
|
* any vertex buffer offset/locking issues. */
|
|
D3DCOLOR col[4];
|
|
Vertex quad[4];
|
|
float x1, y1, x2, y2;
|
|
const float *c = draw->coords->color;
|
|
|
|
/* Per-vertex colors: map the 16-float color array directly
|
|
* to the 4 quad vertices in order. */
|
|
/* The color array is laid out for bottom-up coordinates
|
|
* (vertex 0,1 = bottom, vertex 2,3 = top) as used by d3d10/d3d11.
|
|
* Since this path un-flips Y (top-down ortho), swap the top and
|
|
* bottom vertex color pairs so gradients render correctly. */
|
|
if (c)
|
|
{
|
|
col[0] = D3DCOLOR_ARGB((int)(c[11]*0xFF), (int)(c[ 8]*0xFF), (int)(c[ 9]*0xFF), (int)(c[10]*0xFF));
|
|
col[1] = D3DCOLOR_ARGB((int)(c[15]*0xFF), (int)(c[12]*0xFF), (int)(c[13]*0xFF), (int)(c[14]*0xFF));
|
|
col[2] = D3DCOLOR_ARGB((int)(c[ 3]*0xFF), (int)(c[ 0]*0xFF), (int)(c[ 1]*0xFF), (int)(c[ 2]*0xFF));
|
|
col[3] = D3DCOLOR_ARGB((int)(c[ 7]*0xFF), (int)(c[ 4]*0xFF), (int)(c[ 5]*0xFF), (int)(c[ 6]*0xFF));
|
|
}
|
|
else
|
|
{
|
|
col[0] = col[1] = col[2] = col[3] = D3DCOLOR_ARGB(0xFF, 0xFF, 0xFF, 0xFF);
|
|
}
|
|
|
|
/* Normalize to [0,1] range.
|
|
* Both ozone_draw_icon and gfx_display_draw_quad pre-flip Y
|
|
* (draw.y = height - y - h). The Y-flip here undoes that,
|
|
* then topdown_ortho applies the correct top-down mapping. */
|
|
x1 = draw->x / (float)video_width;
|
|
y1 = ((float)video_height - draw->y - draw->height) / (float)video_height;
|
|
x2 = (draw->x + draw->width) / (float)video_width;
|
|
y2 = ((float)video_height - draw->y) / (float)video_height;
|
|
|
|
/* Apply scale_factor: scale the quad around its center,
|
|
* matching D3D10's geometry shader params.scaling behavior. */
|
|
if (draw->scale_factor && draw->scale_factor != 1.0f)
|
|
{
|
|
float cx = (x1 + x2) * 0.5f;
|
|
float cy = (y1 + y2) * 0.5f;
|
|
float hw = (x2 - x1) * 0.5f * draw->scale_factor;
|
|
float hh = (y2 - y1) * 0.5f * draw->scale_factor;
|
|
x1 = cx - hw;
|
|
y1 = cy - hh;
|
|
x2 = cx + hw;
|
|
y2 = cy + hh;
|
|
}
|
|
|
|
/* Apply draw->rotation around the quad center. Rotation is
|
|
* computed in pixel space (using draw->width / draw->height as
|
|
* the icon's true square extents) and then converted back to
|
|
* normalised [0,1], so a non-square viewport does not skew the
|
|
* rotated icon. */
|
|
if (draw->rotation != 0.0f)
|
|
{
|
|
float cx = (x1 + x2) * 0.5f;
|
|
float cy = (y1 + y2) * 0.5f;
|
|
float half_w = draw->width * 0.5f;
|
|
float half_h = draw->height * 0.5f;
|
|
if (draw->scale_factor && draw->scale_factor != 1.0f)
|
|
{
|
|
half_w *= draw->scale_factor;
|
|
half_h *= draw->scale_factor;
|
|
}
|
|
{
|
|
float c = cosf(draw->rotation);
|
|
float s = sinf(draw->rotation);
|
|
float inv_vw = 1.0f / (float)video_width;
|
|
float inv_vh = 1.0f / (float)video_height;
|
|
/* Pixel-space corner offsets (dx, dy) for each of the
|
|
* four quad vertices. Order matches the (x1,y1)..
|
|
* (x2,y2) layout used immediately below. */
|
|
float ox[4] = { -half_w, half_w, -half_w, half_w };
|
|
float oy[4] = { -half_h, -half_h, half_h, half_h };
|
|
float rx[4], ry[4];
|
|
int k;
|
|
for (k = 0; k < 4; k++)
|
|
{
|
|
rx[k] = (ox[k] * c - oy[k] * s) * inv_vw;
|
|
ry[k] = (ox[k] * s + oy[k] * c) * inv_vh;
|
|
}
|
|
quad[0].x = cx + rx[0]; quad[0].y = cy + ry[0]; quad[0].z = 0.5f;
|
|
quad[0].u = 0.0f; quad[0].v = 0.0f; quad[0].color = col[0];
|
|
|
|
quad[1].x = cx + rx[1]; quad[1].y = cy + ry[1]; quad[1].z = 0.5f;
|
|
quad[1].u = 1.0f; quad[1].v = 0.0f; quad[1].color = col[1];
|
|
|
|
quad[2].x = cx + rx[2]; quad[2].y = cy + ry[2]; quad[2].z = 0.5f;
|
|
quad[2].u = 0.0f; quad[2].v = 1.0f; quad[2].color = col[2];
|
|
|
|
quad[3].x = cx + rx[3]; quad[3].y = cy + ry[3]; quad[3].z = 0.5f;
|
|
quad[3].u = 1.0f; quad[3].v = 1.0f; quad[3].color = col[3];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
quad[0].x = x1; quad[0].y = y1; quad[0].z = 0.5f;
|
|
quad[0].u = 0.0f; quad[0].v = 0.0f; quad[0].color = col[0];
|
|
|
|
quad[1].x = x2; quad[1].y = y1; quad[1].z = 0.5f;
|
|
quad[1].u = 1.0f; quad[1].v = 0.0f; quad[1].color = col[1];
|
|
|
|
quad[2].x = x1; quad[2].y = y2; quad[2].z = 0.5f;
|
|
quad[2].u = 0.0f; quad[2].v = 1.0f; quad[2].color = col[2];
|
|
|
|
quad[3].x = x2; quad[3].y = y2; quad[3].z = 0.5f;
|
|
quad[3].u = 1.0f; quad[3].v = 1.0f; quad[3].color = col[3];
|
|
}
|
|
|
|
/* Top-down ortho: maps X [0,1]→[-1,1], Y [0,1]→[1,-1] (Y=0 at top).
|
|
* Row-major layout for mul(vector, matrix) in the stock HLSL vertex shader.
|
|
* Translation goes in the 4th row (D3D convention). */
|
|
{
|
|
static const float topdown_ortho[16] = {
|
|
2.0f, 0.0f, 0.0f, 0.0f,
|
|
0.0f, -2.0f, 0.0f, 0.0f,
|
|
0.0f, 0.0f, 1.0f, 0.0f,
|
|
-1.0f, 1.0f, 0.0f, 1.0f
|
|
};
|
|
IDirect3DDevice9_SetVertexShaderConstantF(d3d->dev,
|
|
0, topdown_ortho, 4);
|
|
}
|
|
|
|
gfx_display_d3d9_bind_texture(draw, d3d);
|
|
|
|
IDirect3DDevice9_DrawPrimitiveUP(dev, D3DPT_TRIANGLESTRIP,
|
|
2, quad, sizeof(Vertex));
|
|
|
|
/* DrawPrimitiveUP unbinds the stream source, re-bind it */
|
|
IDirect3DDevice9_SetStreamSource(dev, 0,
|
|
(LPDIRECT3DVERTEXBUFFER9)d3d->menu_display.buffer,
|
|
0, sizeof(Vertex));
|
|
return;
|
|
}
|
|
|
|
/* Multi-vertex path: explicit vertex/tex_coord/color arrays */
|
|
if ((d3d->menu_display.offset + vertex_count)
|
|
> (unsigned)d3d->menu_display.size)
|
|
d3d->menu_display.offset = 0;
|
|
|
|
IDirect3DVertexBuffer9_Lock((LPDIRECT3DVERTEXBUFFER9)
|
|
d3d->menu_display.buffer, 0, 0, (void**)&pv,
|
|
d3d->menu_display.offset == 0
|
|
? D3DLOCK_DISCARD : D3DLOCK_NOOVERWRITE);
|
|
if (!pv)
|
|
return;
|
|
|
|
pv += d3d->menu_display.offset;
|
|
vertex = draw->coords->vertex;
|
|
tex_coord = draw->coords->tex_coord;
|
|
color = draw->coords->color;
|
|
|
|
if (!vertex)
|
|
vertex = &d3d9_hlsl_vertexes[0];
|
|
if (!tex_coord)
|
|
tex_coord = &d3d9_hlsl_tex_coords[0];
|
|
|
|
for (i = 0; i < draw->coords->vertices; i++)
|
|
{
|
|
int colors[4];
|
|
|
|
colors[0] = *color++ * 0xFF;
|
|
colors[1] = *color++ * 0xFF;
|
|
colors[2] = *color++ * 0xFF;
|
|
colors[3] = *color++ * 0xFF;
|
|
|
|
pv[i].x = *vertex++;
|
|
pv[i].y = *vertex++;
|
|
pv[i].z = 0.5f;
|
|
pv[i].u = *tex_coord++;
|
|
pv[i].v = *tex_coord++;
|
|
|
|
pv[i].color =
|
|
D3DCOLOR_ARGB(
|
|
colors[3], /* A */
|
|
colors[0], /* R */
|
|
colors[1], /* G */
|
|
colors[2] /* B */
|
|
);
|
|
}
|
|
IDirect3DVertexBuffer9_Unlock((LPDIRECT3DVERTEXBUFFER9)
|
|
d3d->menu_display.buffer);
|
|
|
|
/* Multi-vertex coords from gfx_display_draw_texture_slice and
|
|
* similar are in bottom-up [0,1] space (Y=0 at bottom), matching
|
|
* D3D10's VIDEO_SHADER_STOCK_BLEND ortho.
|
|
* Use bottom-up ortho: X [0,1]→[-1,1], Y [0,1]→[-1,1]. */
|
|
{
|
|
/* Row-major layout for mul(vector, matrix) in the stock HLSL vertex shader. */
|
|
static const float bottomup_ortho[16] = {
|
|
2.0f, 0.0f, 0.0f, 0.0f,
|
|
0.0f, 2.0f, 0.0f, 0.0f,
|
|
0.0f, 0.0f, 1.0f, 0.0f,
|
|
-1.0f, -1.0f, 0.0f, 1.0f
|
|
};
|
|
IDirect3DDevice9_SetVertexShaderConstantF(d3d->dev,
|
|
0, bottomup_ortho, 4);
|
|
}
|
|
|
|
if (draw && draw->texture)
|
|
gfx_display_d3d9_bind_texture(draw, d3d);
|
|
|
|
start = d3d->menu_display.offset;
|
|
|
|
/* Menu draws use a triangle-strip layout. Harden against vertices < 3
|
|
* (the count formula vertices - 2 would otherwise underflow the
|
|
* unsigned subtraction and pass a huge primitive count to the GPU --
|
|
* see d3d8.c for the matching guard). */
|
|
if (draw->coords->vertices < 3)
|
|
{
|
|
d3d->menu_display.offset += draw->coords->vertices;
|
|
return;
|
|
}
|
|
count = draw->coords->vertices - 2;
|
|
|
|
IDirect3DDevice9_DrawPrimitive(dev, D3DPT_TRIANGLESTRIP, start, count);
|
|
|
|
d3d->menu_display.offset += draw->coords->vertices;
|
|
}
|
|
|
|
static void gfx_display_d3d9_hlsl_draw_pipeline(
|
|
gfx_display_ctx_draw_t *draw,
|
|
gfx_display_t *p_disp,
|
|
void *data, unsigned video_width, unsigned video_height)
|
|
{
|
|
static float t = 0.0f;
|
|
video_coord_array_t *ca = NULL;
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
|
|
if (!d3d || !draw)
|
|
return;
|
|
|
|
ca = &p_disp->dispca;
|
|
|
|
draw->x = 0;
|
|
draw->y = 0;
|
|
draw->coords = NULL;
|
|
draw->matrix_data = NULL;
|
|
|
|
if (ca)
|
|
draw->coords = (struct video_coords*)&ca->coords;
|
|
|
|
switch (draw->pipeline_id)
|
|
{
|
|
case VIDEO_SHADER_MENU:
|
|
case VIDEO_SHADER_MENU_2:
|
|
{
|
|
/* Create a pipeline vertex buffer from the coordinate
|
|
* array data if it doesn't already exist.
|
|
* Adapted from d3d10 gfx_display_d3d10_draw_pipeline. */
|
|
if (!d3d9_hlsl_menu_pipeline_vbo && ca->coords.vertices)
|
|
{
|
|
unsigned i;
|
|
Vertex *verts = NULL;
|
|
unsigned vcount = ca->coords.vertices;
|
|
|
|
d3d9_hlsl_menu_pipeline_vbo = NULL;
|
|
if (!SUCCEEDED(IDirect3DDevice9_CreateVertexBuffer(
|
|
d3d->dev,
|
|
vcount * sizeof(Vertex),
|
|
D3DUSAGE_WRITEONLY, 0,
|
|
D3DPOOL_DEFAULT,
|
|
(LPDIRECT3DVERTEXBUFFER9*)&d3d9_hlsl_menu_pipeline_vbo, NULL)))
|
|
d3d9_hlsl_menu_pipeline_vbo = NULL;
|
|
|
|
if (d3d9_hlsl_menu_pipeline_vbo)
|
|
{
|
|
IDirect3DVertexBuffer9_Lock(
|
|
(LPDIRECT3DVERTEXBUFFER9)d3d9_hlsl_menu_pipeline_vbo,
|
|
0, 0, (void**)&verts, 0);
|
|
|
|
if (verts)
|
|
{
|
|
for (i = 0; i < vcount; i++)
|
|
{
|
|
verts[i].x = ca->coords.vertex[i * 2 + 0];
|
|
verts[i].y = ca->coords.vertex[i * 2 + 1];
|
|
verts[i].z = 0.5f;
|
|
verts[i].u = 0.0f;
|
|
verts[i].v = 0.0f;
|
|
verts[i].color = D3DCOLOR_ARGB(0xFF, 0xFF, 0xFF, 0xFF);
|
|
}
|
|
|
|
IDirect3DVertexBuffer9_Unlock(
|
|
(LPDIRECT3DVERTEXBUFFER9)d3d9_hlsl_menu_pipeline_vbo);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (d3d9_hlsl_menu_pipeline_vbo)
|
|
{
|
|
IDirect3DDevice9_SetStreamSource(d3d->dev, 0,
|
|
(LPDIRECT3DVERTEXBUFFER9)d3d9_hlsl_menu_pipeline_vbo,
|
|
0, sizeof(Vertex));
|
|
}
|
|
|
|
draw->coords->vertices = ca->coords.vertices;
|
|
|
|
/* Set pipeline blend state — ribbon uses multiplicative blend
|
|
* (DESTCOLOR + ONE) matching D3D11's blend_pipeline. */
|
|
IDirect3DDevice9_SetRenderState(d3d->dev,
|
|
D3DRS_SRCBLEND, D3DBLEND_DESTCOLOR);
|
|
IDirect3DDevice9_SetRenderState(d3d->dev,
|
|
D3DRS_DESTBLEND, D3DBLEND_ONE);
|
|
IDirect3DDevice9_SetRenderState(d3d->dev,
|
|
D3DRS_ALPHABLENDENABLE, true);
|
|
break;
|
|
}
|
|
|
|
case VIDEO_SHADER_MENU_3:
|
|
case VIDEO_SHADER_MENU_4:
|
|
case VIDEO_SHADER_MENU_5:
|
|
case VIDEO_SHADER_MENU_6:
|
|
{
|
|
static struct video_coords blank_coords = {0};
|
|
|
|
/* Fullscreen quad for snow/bokeh/snowflake effects */
|
|
if (!d3d9_hlsl_menu_fullscreen_vbo)
|
|
{
|
|
Vertex verts[4];
|
|
verts[0].x = 0.0f; verts[0].y = 0.0f; verts[0].z = 0.0f;
|
|
verts[0].u = 0.0f; verts[0].v = 1.0f;
|
|
verts[0].color = D3DCOLOR_ARGB(0xFF, 0xFF, 0xFF, 0xFF);
|
|
verts[1].x = 1.0f; verts[1].y = 0.0f; verts[1].z = 0.0f;
|
|
verts[1].u = 1.0f; verts[1].v = 1.0f;
|
|
verts[1].color = D3DCOLOR_ARGB(0xFF, 0xFF, 0xFF, 0xFF);
|
|
verts[2].x = 0.0f; verts[2].y = 1.0f; verts[2].z = 0.0f;
|
|
verts[2].u = 0.0f; verts[2].v = 0.0f;
|
|
verts[2].color = D3DCOLOR_ARGB(0xFF, 0xFF, 0xFF, 0xFF);
|
|
verts[3].x = 1.0f; verts[3].y = 1.0f; verts[3].z = 0.0f;
|
|
verts[3].u = 1.0f; verts[3].v = 0.0f;
|
|
verts[3].color = D3DCOLOR_ARGB(0xFF, 0xFF, 0xFF, 0xFF);
|
|
|
|
if (SUCCEEDED(IDirect3DDevice9_CreateVertexBuffer(
|
|
d3d->dev, 4 * sizeof(Vertex),
|
|
D3DUSAGE_WRITEONLY, 0, D3DPOOL_DEFAULT,
|
|
(LPDIRECT3DVERTEXBUFFER9*)&d3d9_hlsl_menu_fullscreen_vbo, NULL)))
|
|
{
|
|
void *lock = NULL;
|
|
IDirect3DVertexBuffer9_Lock(
|
|
(LPDIRECT3DVERTEXBUFFER9)d3d9_hlsl_menu_fullscreen_vbo,
|
|
0, 0, &lock, 0);
|
|
if (lock)
|
|
{
|
|
memcpy(lock, verts, sizeof(verts));
|
|
IDirect3DVertexBuffer9_Unlock(
|
|
(LPDIRECT3DVERTEXBUFFER9)d3d9_hlsl_menu_fullscreen_vbo);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (d3d9_hlsl_menu_fullscreen_vbo)
|
|
{
|
|
IDirect3DDevice9_SetStreamSource(d3d->dev, 0,
|
|
(LPDIRECT3DVERTEXBUFFER9)d3d9_hlsl_menu_fullscreen_vbo,
|
|
0, sizeof(Vertex));
|
|
}
|
|
|
|
/* Use separate coords to avoid mutating the shared
|
|
* ca->coords.vertices (which ribbon needs at 8064). */
|
|
blank_coords.vertices = 4;
|
|
draw->coords = &blank_coords;
|
|
|
|
/* Set blend state for particle effects */
|
|
IDirect3DDevice9_SetRenderState(d3d->dev,
|
|
D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
|
|
IDirect3DDevice9_SetRenderState(d3d->dev,
|
|
D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
|
|
IDirect3DDevice9_SetRenderState(d3d->dev,
|
|
D3DRS_ALPHABLENDENABLE, true);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
/* Update time uniform - mirrors d3d10 ubo_values.time increment.
|
|
* Wrap at 65536 to keep fp32 increments precise. 0.01 stays
|
|
* exactly representable up to t ~ 167772 (where 0.5*ulp first
|
|
* exceeds 0.01), so 65536 has wide margin and wraps roughly
|
|
* every 30 h of cumulative menu time, making the discontinuity
|
|
* effectively unobservable. */
|
|
t += 0.01f;
|
|
if (t > 65536.0f)
|
|
t -= 65536.0f;
|
|
|
|
{
|
|
hlsl_renderchain_t *_chain = (hlsl_renderchain_t*)d3d->renderchain_data;
|
|
if (_chain && _chain->pipeline_inited
|
|
&& draw->pipeline_id <= VIDEO_SHADER_MENU
|
|
&& draw->pipeline_id >= VIDEO_SHADER_MENU_6)
|
|
{
|
|
unsigned idx = VIDEO_SHADER_MENU - draw->pipeline_id;
|
|
hlsl_pass_data_t *pd = &_chain->pipeline_data[idx];
|
|
|
|
/* Set time uniform for all pipeline shaders */
|
|
d3d9_hlsl_set_param_1f(pd->vs_bytecode, pd->vs_bytecode_dwords,
|
|
true, d3d->dev, "time", &t);
|
|
d3d9_hlsl_set_param_1f(pd->ps_bytecode, pd->ps_bytecode_dwords,
|
|
false, d3d->dev, "time", &t);
|
|
|
|
/* Set OutputSize for snow/bokeh/snowflake shaders */
|
|
if (draw->pipeline_id <= VIDEO_SHADER_MENU_3)
|
|
{
|
|
D3DVIEWPORT9 vp;
|
|
IDirect3DDevice9_GetViewport(d3d->dev, &vp);
|
|
{
|
|
float output_size[4];
|
|
int reg;
|
|
output_size[0] = (float)vp.Width;
|
|
output_size[1] = (float)vp.Height;
|
|
output_size[2] = 0.0f;
|
|
output_size[3] = 0.0f;
|
|
/* Set OutputSize as a full register (float2 occupies one register) */
|
|
reg = d3d9_hlsl_ctab_find_register(
|
|
pd->vs_bytecode, pd->vs_bytecode_dwords,
|
|
"OutputSize", NULL, NULL);
|
|
if (reg >= 0)
|
|
d3d9_hlsl_set_vs_const(d3d->dev, reg, output_size, 1);
|
|
reg = d3d9_hlsl_ctab_find_register(
|
|
pd->ps_bytecode, pd->ps_bytecode_dwords,
|
|
"OutputSize", NULL, NULL);
|
|
if (reg >= 0)
|
|
d3d9_hlsl_set_ps_const(d3d->dev, reg, output_size, 1);
|
|
}
|
|
}
|
|
|
|
/* Set alpha for ribbon shaders */
|
|
if (draw->pipeline_id >= VIDEO_SHADER_MENU_2)
|
|
{
|
|
float alpha_val = draw->color ? draw->color[3] : 1.0f;
|
|
d3d9_hlsl_set_param_1f(pd->vs_bytecode, pd->vs_bytecode_dwords,
|
|
true, d3d->dev, "alpha", &alpha_val);
|
|
d3d9_hlsl_set_param_1f(pd->ps_bytecode, pd->ps_bytecode_dwords,
|
|
false, d3d->dev, "alpha", &alpha_val);
|
|
}
|
|
}
|
|
else if (_chain)
|
|
{
|
|
d3d9_hlsl_set_param_1f(
|
|
_chain->stock_data.vs_bytecode,
|
|
_chain->stock_data.vs_bytecode_dwords, true,
|
|
d3d->dev, "IN.frame_count", &t);
|
|
d3d9_hlsl_set_param_1f(
|
|
_chain->stock_data.ps_bytecode,
|
|
_chain->stock_data.ps_bytecode_dwords, false,
|
|
d3d->dev, "IN.frame_count", &t);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void gfx_display_d3d9_hlsl_scissor_begin(
|
|
void *data,
|
|
unsigned video_width, unsigned video_height,
|
|
int x, int y, unsigned width, unsigned height)
|
|
{
|
|
RECT rect;
|
|
d3d9_video_t *d3d9 = (d3d9_video_t*)data;
|
|
|
|
if (!d3d9)
|
|
return;
|
|
|
|
rect.left = x;
|
|
rect.top = y;
|
|
rect.right = width + x;
|
|
rect.bottom = height + y;
|
|
|
|
IDirect3DDevice9_SetScissorRect(d3d9->dev, &rect);
|
|
}
|
|
|
|
static void gfx_display_d3d9_hlsl_scissor_end(void *data,
|
|
unsigned video_width, unsigned video_height)
|
|
{
|
|
RECT rect;
|
|
d3d9_video_t *d3d9 = (d3d9_video_t*)data;
|
|
|
|
if (!d3d9)
|
|
return;
|
|
|
|
rect.left = 0;
|
|
rect.top = 0;
|
|
rect.right = video_width;
|
|
rect.bottom = video_height;
|
|
|
|
IDirect3DDevice9_SetScissorRect(d3d9->dev, &rect);
|
|
}
|
|
|
|
/*
|
|
* FONT DRIVER
|
|
*/
|
|
|
|
typedef struct
|
|
{
|
|
LPDIRECT3DTEXTURE9 texture;
|
|
const font_renderer_driver_t *font_driver;
|
|
void *font_data;
|
|
struct font_atlas *atlas;
|
|
unsigned tex_width;
|
|
unsigned tex_height;
|
|
/* Scratch buffer to avoid per-line malloc/free in font rendering */
|
|
Vertex *scratch_verts;
|
|
unsigned scratch_capacity; /* in Vertex count */
|
|
} d3d9_font_t;
|
|
|
|
static void *d3d9_font_init(void *data,
|
|
const char *font_path, float font_size,
|
|
bool is_threaded)
|
|
{
|
|
unsigned i, j;
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
d3d9_font_t *font = (d3d9_font_t*)calloc(1, sizeof(*font));
|
|
|
|
if (!font)
|
|
return NULL;
|
|
|
|
if (!font_renderer_create_default(
|
|
&font->font_driver, &font->font_data,
|
|
font_path, font_size, FONT_ATLAS_FORMAT_A8))
|
|
{
|
|
free(font);
|
|
return NULL;
|
|
}
|
|
|
|
font->atlas = font->font_driver->get_atlas(font->font_data);
|
|
font->tex_width = font->atlas->width;
|
|
font->tex_height = font->atlas->height;
|
|
|
|
/* Create an A8R8G8B8 texture from the A8 atlas buffer.
|
|
* D3D9 doesn't universally support D3DFMT_A8
|
|
* as a texture format, so we expand it. */
|
|
font->texture = NULL;
|
|
IDirect3DDevice9_CreateTexture(d3d->dev,
|
|
font->tex_width, font->tex_height, 1,
|
|
0, D3DFMT_A8R8G8B8,
|
|
D3DPOOL_MANAGED,
|
|
(struct IDirect3DTexture9**)&font->texture, NULL);
|
|
|
|
if (font->texture)
|
|
{
|
|
D3DLOCKED_RECT lr;
|
|
|
|
if (SUCCEEDED(IDirect3DTexture9_LockRect(
|
|
font->texture, 0, &lr, NULL, 0)))
|
|
{
|
|
for (j = 0; j < font->atlas->height; j++)
|
|
{
|
|
uint32_t *dst = (uint32_t*)((uint8_t*)lr.pBits + j * lr.Pitch);
|
|
const uint8_t *src = font->atlas->buffer + j * font->atlas->width;
|
|
for (i = 0; i < font->atlas->width; i++)
|
|
dst[i] = D3DCOLOR_ARGB(src[i], 0xFF, 0xFF, 0xFF);
|
|
}
|
|
IDirect3DTexture9_UnlockRect(font->texture, 0);
|
|
}
|
|
}
|
|
|
|
font->atlas->dirty = false;
|
|
return font;
|
|
}
|
|
|
|
static void d3d9_font_free(void *data, bool is_threaded)
|
|
{
|
|
d3d9_font_t *font = (d3d9_font_t*)data;
|
|
|
|
if (!font)
|
|
return;
|
|
|
|
if (font->font_driver && font->font_data)
|
|
font->font_driver->free(font->font_data);
|
|
|
|
if (font->texture)
|
|
IDirect3DTexture9_Release(font->texture);
|
|
|
|
free(font->scratch_verts);
|
|
free(font);
|
|
}
|
|
|
|
static int d3d9_font_get_message_width(void *data,
|
|
const char *msg, size_t msg_len, float scale)
|
|
{
|
|
size_t i;
|
|
int delta_x = 0;
|
|
const struct font_glyph *glyph_q = NULL;
|
|
d3d9_font_t *font = (d3d9_font_t*)data;
|
|
|
|
if (!font)
|
|
return 0;
|
|
|
|
glyph_q = font->font_driver->get_glyph(font->font_data, '?');
|
|
|
|
for (i = 0; i < msg_len; i++)
|
|
{
|
|
const struct font_glyph *glyph;
|
|
const char *msg_tmp = &msg[i];
|
|
unsigned code = utf8_walk(&msg_tmp);
|
|
unsigned skip = msg_tmp - &msg[i];
|
|
|
|
if (skip > 1)
|
|
i += skip - 1;
|
|
|
|
if (!(glyph = font->font_driver->get_glyph(font->font_data, code)))
|
|
if (!(glyph = glyph_q))
|
|
continue;
|
|
|
|
delta_x += glyph->advance_x;
|
|
}
|
|
|
|
return delta_x * scale;
|
|
}
|
|
|
|
/* Emit a single glyph quad (6 vertices for two triangles)
|
|
* into the output vertex array. Returns the number of
|
|
* vertices written (always 6). */
|
|
static INLINE unsigned d3d9_font_emit_quad(
|
|
Vertex *pv,
|
|
float x, float y, float w, float h,
|
|
float tex_u, float tex_v, float tex_w, float tex_h,
|
|
D3DCOLOR color)
|
|
{
|
|
/* Triangle 1: top-left, top-right, bottom-left */
|
|
pv[0].x = x;
|
|
pv[0].y = y;
|
|
pv[0].z = 0.5f;
|
|
pv[0].u = tex_u;
|
|
pv[0].v = tex_v;
|
|
pv[0].color = color;
|
|
|
|
pv[1].x = x + w;
|
|
pv[1].y = y;
|
|
pv[1].z = 0.5f;
|
|
pv[1].u = tex_u + tex_w;
|
|
pv[1].v = tex_v;
|
|
pv[1].color = color;
|
|
|
|
pv[2].x = x;
|
|
pv[2].y = y + h;
|
|
pv[2].z = 0.5f;
|
|
pv[2].u = tex_u;
|
|
pv[2].v = tex_v + tex_h;
|
|
pv[2].color = color;
|
|
|
|
/* Triangle 2: top-right, bottom-right, bottom-left */
|
|
pv[3].x = x + w;
|
|
pv[3].y = y;
|
|
pv[3].z = 0.5f;
|
|
pv[3].u = tex_u + tex_w;
|
|
pv[3].v = tex_v;
|
|
pv[3].color = color;
|
|
|
|
pv[4].x = x + w;
|
|
pv[4].y = y + h;
|
|
pv[4].z = 0.5f;
|
|
pv[4].u = tex_u + tex_w;
|
|
pv[4].v = tex_v + tex_h;
|
|
pv[4].color = color;
|
|
|
|
pv[5].x = x;
|
|
pv[5].y = y + h;
|
|
pv[5].z = 0.5f;
|
|
pv[5].u = tex_u;
|
|
pv[5].v = tex_v + tex_h;
|
|
pv[5].color = color;
|
|
|
|
return 6;
|
|
}
|
|
|
|
/* Get or grow the scratch vertex buffer for font rendering.
|
|
* Avoids per-line calloc/free on the hot path. */
|
|
static INLINE Vertex *d3d9_font_get_scratch(
|
|
d3d9_font_t *font, unsigned needed)
|
|
{
|
|
if (needed > font->scratch_capacity)
|
|
{
|
|
unsigned new_cap = needed > 1536 ? needed : 1536; /* 256 glyphs * 6 verts */
|
|
Vertex *tmp = (Vertex*)realloc(font->scratch_verts, new_cap * sizeof(Vertex));
|
|
if (!tmp)
|
|
return NULL;
|
|
font->scratch_verts = tmp;
|
|
font->scratch_capacity = new_cap;
|
|
}
|
|
memset(font->scratch_verts, 0, needed * sizeof(Vertex));
|
|
return font->scratch_verts;
|
|
}
|
|
|
|
static void d3d9_font_render_msg(
|
|
void *userdata, void *data,
|
|
const char *msg, size_t msg_len,
|
|
const struct font_params *params)
|
|
{
|
|
float x, y, scale, drop_mod, drop_alpha;
|
|
enum text_alignment text_align;
|
|
int drop_x, drop_y;
|
|
unsigned r, g, b, alpha;
|
|
D3DCOLOR color, color_dark;
|
|
struct font_line_metrics *line_metrics = NULL;
|
|
float line_height;
|
|
d3d9_font_t *font = (d3d9_font_t*)data;
|
|
d3d9_video_t *d3d = (d3d9_video_t*)userdata;
|
|
unsigned width = 0;
|
|
unsigned height = 0;
|
|
|
|
if (!font || !msg || !*msg)
|
|
return;
|
|
if (!d3d)
|
|
return;
|
|
|
|
width = d3d->vp.full_width;
|
|
height = d3d->vp.full_height;
|
|
if (!width || !height)
|
|
return;
|
|
|
|
if (params)
|
|
{
|
|
x = params->x;
|
|
y = params->y;
|
|
scale = params->scale;
|
|
text_align = params->text_align;
|
|
drop_x = params->drop_x;
|
|
drop_y = params->drop_y;
|
|
drop_mod = params->drop_mod;
|
|
drop_alpha = params->drop_alpha;
|
|
|
|
r = FONT_COLOR_GET_RED(params->color);
|
|
g = FONT_COLOR_GET_GREEN(params->color);
|
|
b = FONT_COLOR_GET_BLUE(params->color);
|
|
alpha = FONT_COLOR_GET_ALPHA(params->color);
|
|
|
|
color = D3DCOLOR_ARGB(alpha, r, g, b);
|
|
}
|
|
else
|
|
{
|
|
settings_t *settings = config_get_ptr();
|
|
float video_msg_pos_x = settings->floats.video_msg_pos_x;
|
|
float video_msg_pos_y = settings->floats.video_msg_pos_y;
|
|
float video_msg_color_r = settings->floats.video_msg_color_r;
|
|
float video_msg_color_g = settings->floats.video_msg_color_g;
|
|
float video_msg_color_b = settings->floats.video_msg_color_b;
|
|
|
|
x = video_msg_pos_x;
|
|
y = video_msg_pos_y;
|
|
scale = 1.0f;
|
|
text_align = TEXT_ALIGN_LEFT;
|
|
|
|
r = (unsigned)(video_msg_color_r * 255);
|
|
g = (unsigned)(video_msg_color_g * 255);
|
|
b = (unsigned)(video_msg_color_b * 255);
|
|
alpha = 255;
|
|
color = D3DCOLOR_ARGB(alpha, r, g, b);
|
|
|
|
drop_x = -2;
|
|
drop_y = -2;
|
|
drop_mod = 0.3f;
|
|
drop_alpha = 1.0f;
|
|
}
|
|
|
|
font->font_driver->get_line_metrics(font->font_data, &line_metrics);
|
|
line_height = line_metrics->height * scale / height;
|
|
|
|
/* Enable alpha blending for font rendering */
|
|
IDirect3DDevice9_SetRenderState(d3d->dev,
|
|
D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
|
|
IDirect3DDevice9_SetRenderState(d3d->dev,
|
|
D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
|
|
IDirect3DDevice9_SetRenderState(d3d->dev,
|
|
D3DRS_ALPHABLENDENABLE, true);
|
|
|
|
/* Use top-down ortho: Y=0→top, Y=1→bottom, matching Ozone coords.
|
|
* Row-major layout for mul(vector, matrix) in the stock HLSL vertex shader. */
|
|
{
|
|
static const float topdown_ortho[16] = {
|
|
2.0f, 0.0f, 0.0f, 0.0f,
|
|
0.0f, -2.0f, 0.0f, 0.0f,
|
|
0.0f, 0.0f, 1.0f, 0.0f,
|
|
-1.0f, 1.0f, 0.0f, 1.0f
|
|
};
|
|
IDirect3DDevice9_SetVertexShaderConstantF(d3d->dev,
|
|
0, topdown_ortho, 4);
|
|
}
|
|
|
|
/* Update atlas texture if dirty */
|
|
if (font->atlas->dirty)
|
|
{
|
|
/* If the atlas dimensions changed (grew), we must recreate
|
|
* the texture to match, otherwise glyphs added after init
|
|
* will have wrong UVs or missing data. */
|
|
bool respecified = false;
|
|
|
|
if ( font->atlas->width != font->tex_width
|
|
|| font->atlas->height != font->tex_height)
|
|
{
|
|
respecified = true;
|
|
if (font->texture)
|
|
IDirect3DTexture9_Release(font->texture);
|
|
|
|
font->tex_width = font->atlas->width;
|
|
font->tex_height = font->atlas->height;
|
|
font->texture = NULL;
|
|
IDirect3DDevice9_CreateTexture(d3d->dev,
|
|
font->tex_width, font->tex_height, 1,
|
|
0, D3DFMT_A8R8G8B8,
|
|
D3DPOOL_MANAGED,
|
|
(struct IDirect3DTexture9**)&font->texture, NULL);
|
|
}
|
|
|
|
if (font->texture)
|
|
{
|
|
unsigned i, j;
|
|
D3DLOCKED_RECT lr;
|
|
RECT rect;
|
|
unsigned x0 = font->atlas->dirty_x0;
|
|
unsigned y0 = font->atlas->dirty_y0;
|
|
unsigned x1 = font->atlas->dirty_x1;
|
|
unsigned y1 = font->atlas->dirty_y1;
|
|
|
|
/* A recreated texture has no previous contents, so the whole
|
|
* atlas must be converted; otherwise only the dirty
|
|
* rectangle tracked by the font renderers needs it. Managed
|
|
* pool textures track locked sub-rects natively. */
|
|
if ( respecified
|
|
|| x1 <= x0 || y1 <= y0
|
|
|| x1 > (unsigned)font->atlas->width
|
|
|| y1 > (unsigned)font->atlas->height)
|
|
{
|
|
x0 = 0;
|
|
y0 = 0;
|
|
x1 = font->atlas->width;
|
|
y1 = font->atlas->height;
|
|
}
|
|
rect.left = (LONG)x0;
|
|
rect.top = (LONG)y0;
|
|
rect.right = (LONG)x1;
|
|
rect.bottom = (LONG)y1;
|
|
|
|
if (SUCCEEDED(IDirect3DTexture9_LockRect(
|
|
font->texture, 0, &lr, &rect, 0)))
|
|
{
|
|
/* lr.pBits addresses the top-left of the locked rect */
|
|
for (j = 0; j < y1 - y0; j++)
|
|
{
|
|
uint32_t *dst = (uint32_t*)((uint8_t*)lr.pBits + j * lr.Pitch);
|
|
const uint8_t *src = font->atlas->buffer
|
|
+ (size_t)(y0 + j) * font->atlas->width + x0;
|
|
for (i = 0; i < x1 - x0; i++)
|
|
dst[i] = D3DCOLOR_ARGB(src[i], 0xFF, 0xFF, 0xFF);
|
|
}
|
|
IDirect3DTexture9_UnlockRect(font->texture, 0);
|
|
}
|
|
}
|
|
font->atlas->dirty = false;
|
|
}
|
|
|
|
/* Render each line, handling newlines like d3d10 does.
|
|
* Callers in d3d9_hlsl_frame wrap in BeginScene/EndScene. */
|
|
|
|
/* Set vertex declaration and stock shader for DrawPrimitiveUP */
|
|
IDirect3DDevice9_SetVertexDeclaration(d3d->dev,
|
|
(LPDIRECT3DVERTEXDECLARATION9)d3d->menu_display.decl);
|
|
{
|
|
hlsl_renderchain_t *_chain = (hlsl_renderchain_t*)d3d->renderchain_data;
|
|
if (_chain)
|
|
{
|
|
IDirect3DDevice9_SetVertexShader(d3d->dev, (LPDIRECT3DVERTEXSHADER9)(&_chain->stock_shader)->vprg);
|
|
IDirect3DDevice9_SetPixelShader(d3d->dev, (LPDIRECT3DPIXELSHADER9)(&_chain->stock_shader)->fprg);
|
|
}
|
|
}
|
|
|
|
{
|
|
int lines = 0;
|
|
bool has_drop = drop_x || drop_y;
|
|
const char *m = msg;
|
|
|
|
if (has_drop)
|
|
{
|
|
unsigned r_dark = r * drop_mod;
|
|
unsigned g_dark = g * drop_mod;
|
|
unsigned b_dark = b * drop_mod;
|
|
unsigned alpha_dark = alpha * drop_alpha;
|
|
color_dark = D3DCOLOR_ARGB(alpha_dark, r_dark, g_dark, b_dark);
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
const char *end = m;
|
|
size_t msg_len;
|
|
|
|
while (*end && *end != '\n')
|
|
end++;
|
|
msg_len = (size_t)(end - m);
|
|
|
|
if (msg_len > 0)
|
|
{
|
|
unsigned i;
|
|
float inv_viewport_w = 1.0f / (float)width;
|
|
float inv_viewport_h = 1.0f / (float)height;
|
|
float inv_tex_w = 1.0f / (float)font->tex_width;
|
|
float inv_tex_h = 1.0f / (float)font->tex_height;
|
|
const struct font_glyph *glyph_q = font->font_driver->get_glyph(font->font_data, '?');
|
|
float line_y = y - (float)lines * line_height;
|
|
|
|
/* Drop shadow pass */
|
|
if (has_drop)
|
|
{
|
|
float drop_pos_x = x + scale * drop_x / (float)width;
|
|
float drop_pos_y = line_y + scale * drop_y / (float)height;
|
|
int lx = roundf(drop_pos_x * width);
|
|
int ly = roundf((1.0f - drop_pos_y) * height);
|
|
unsigned vert_count = 0;
|
|
Vertex *verts = d3d9_font_get_scratch(font, msg_len * 6);
|
|
|
|
if (verts)
|
|
{
|
|
if (text_align == TEXT_ALIGN_RIGHT || text_align == TEXT_ALIGN_CENTER)
|
|
{
|
|
int width_accum = 0;
|
|
const char *scan = m;
|
|
const char *scan_end = m + msg_len;
|
|
while (scan < scan_end)
|
|
{
|
|
const struct font_glyph *glyph;
|
|
uint32_t code = utf8_walk(&scan);
|
|
if (!(glyph = font->font_driver->get_glyph(font->font_data, code)))
|
|
if (!(glyph = glyph_q))
|
|
continue;
|
|
width_accum += glyph->advance_x;
|
|
}
|
|
if (text_align == TEXT_ALIGN_RIGHT)
|
|
drop_pos_x -= (float)(width_accum * scale) / (float)width;
|
|
else
|
|
drop_pos_x -= (float)(width_accum * scale) / (float)width / 2.0f;
|
|
lx = roundf(drop_pos_x * width);
|
|
}
|
|
|
|
for (i = 0; i < msg_len; i++)
|
|
{
|
|
const struct font_glyph *glyph;
|
|
const char *msg_tmp = &m[i];
|
|
unsigned code = utf8_walk(&msg_tmp);
|
|
unsigned skip = msg_tmp - &m[i];
|
|
|
|
if (skip > 1)
|
|
i += skip - 1;
|
|
|
|
if (!(glyph = font->font_driver->get_glyph(font->font_data, code)))
|
|
if (!(glyph = glyph_q))
|
|
continue;
|
|
|
|
vert_count += d3d9_font_emit_quad(
|
|
&verts[vert_count],
|
|
(lx + glyph->draw_offset_x * scale) * inv_viewport_w,
|
|
(ly + glyph->draw_offset_y * scale) * inv_viewport_h,
|
|
glyph->width * scale * inv_viewport_w,
|
|
glyph->height * scale * inv_viewport_h,
|
|
glyph->atlas_offset_x * inv_tex_w,
|
|
glyph->atlas_offset_y * inv_tex_h,
|
|
glyph->width * inv_tex_w,
|
|
glyph->height * inv_tex_h,
|
|
color_dark);
|
|
|
|
lx += glyph->advance_x * scale;
|
|
ly += glyph->advance_y * scale;
|
|
}
|
|
|
|
if (vert_count > 0)
|
|
{
|
|
IDirect3DDevice9_SetTexture(d3d->dev, 0,
|
|
(IDirect3DBaseTexture9*)font->texture);
|
|
IDirect3DDevice9_SetSamplerState(d3d->dev,
|
|
0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
|
|
IDirect3DDevice9_SetSamplerState(d3d->dev,
|
|
0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
|
|
IDirect3DDevice9_SetSamplerState(d3d->dev,
|
|
0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
|
|
IDirect3DDevice9_SetSamplerState(d3d->dev,
|
|
0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
|
|
|
|
IDirect3DDevice9_DrawPrimitiveUP(d3d->dev,
|
|
D3DPT_TRIANGLELIST,
|
|
vert_count / 3,
|
|
verts,
|
|
sizeof(Vertex));
|
|
|
|
IDirect3DDevice9_SetStreamSource(d3d->dev, 0,
|
|
(LPDIRECT3DVERTEXBUFFER9)d3d->menu_display.buffer,
|
|
0, sizeof(Vertex));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Main text pass */
|
|
{
|
|
float pos_x = x;
|
|
float pos_y = line_y;
|
|
int lx = roundf(pos_x * width);
|
|
int ly = roundf((1.0f - pos_y) * height);
|
|
unsigned vert_count = 0;
|
|
Vertex *verts = d3d9_font_get_scratch(font, msg_len * 6);
|
|
|
|
if (verts)
|
|
{
|
|
if (text_align == TEXT_ALIGN_RIGHT || text_align == TEXT_ALIGN_CENTER)
|
|
{
|
|
int width_accum = 0;
|
|
const char *scan = m;
|
|
const char *scan_end = m + msg_len;
|
|
while (scan < scan_end)
|
|
{
|
|
const struct font_glyph *glyph;
|
|
uint32_t code = utf8_walk(&scan);
|
|
if (!(glyph = font->font_driver->get_glyph(font->font_data, code)))
|
|
if (!(glyph = glyph_q))
|
|
continue;
|
|
width_accum += glyph->advance_x;
|
|
}
|
|
if (text_align == TEXT_ALIGN_RIGHT)
|
|
pos_x -= (float)(width_accum * scale) / (float)width;
|
|
else
|
|
pos_x -= (float)(width_accum * scale) / (float)width / 2.0f;
|
|
lx = roundf(pos_x * width);
|
|
}
|
|
|
|
for (i = 0; i < msg_len; i++)
|
|
{
|
|
const struct font_glyph *glyph;
|
|
const char *msg_tmp = &m[i];
|
|
unsigned code = utf8_walk(&msg_tmp);
|
|
unsigned skip = msg_tmp - &m[i];
|
|
|
|
if (skip > 1)
|
|
i += skip - 1;
|
|
|
|
if (!(glyph = font->font_driver->get_glyph(font->font_data, code)))
|
|
if (!(glyph = glyph_q))
|
|
continue;
|
|
|
|
vert_count += d3d9_font_emit_quad(
|
|
&verts[vert_count],
|
|
(lx + glyph->draw_offset_x * scale) * inv_viewport_w,
|
|
(ly + glyph->draw_offset_y * scale) * inv_viewport_h,
|
|
glyph->width * scale * inv_viewport_w,
|
|
glyph->height * scale * inv_viewport_h,
|
|
glyph->atlas_offset_x * inv_tex_w,
|
|
glyph->atlas_offset_y * inv_tex_h,
|
|
glyph->width * inv_tex_w,
|
|
glyph->height * inv_tex_h,
|
|
color);
|
|
|
|
lx += glyph->advance_x * scale;
|
|
ly += glyph->advance_y * scale;
|
|
}
|
|
|
|
if (vert_count > 0)
|
|
{
|
|
IDirect3DDevice9_SetTexture(d3d->dev, 0,
|
|
(IDirect3DBaseTexture9*)font->texture);
|
|
IDirect3DDevice9_SetSamplerState(d3d->dev,
|
|
0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
|
|
IDirect3DDevice9_SetSamplerState(d3d->dev,
|
|
0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
|
|
IDirect3DDevice9_SetSamplerState(d3d->dev,
|
|
0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
|
|
IDirect3DDevice9_SetSamplerState(d3d->dev,
|
|
0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
|
|
|
|
IDirect3DDevice9_DrawPrimitiveUP(d3d->dev,
|
|
D3DPT_TRIANGLELIST,
|
|
vert_count / 3,
|
|
verts,
|
|
sizeof(Vertex));
|
|
|
|
IDirect3DDevice9_SetStreamSource(d3d->dev, 0,
|
|
(LPDIRECT3DVERTEXBUFFER9)d3d->menu_display.buffer,
|
|
0, sizeof(Vertex));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (*end != '\n')
|
|
break;
|
|
m = end + 1;
|
|
lines++;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
static const struct font_glyph *d3d9_font_get_glyph(
|
|
void *data, uint32_t code)
|
|
{
|
|
d3d9_font_t *font = (d3d9_font_t*)data;
|
|
if (font && font->font_driver)
|
|
return font->font_driver->get_glyph(
|
|
(void*)font->font_data, code);
|
|
return NULL;
|
|
}
|
|
|
|
static bool d3d9_font_get_line_metrics(
|
|
void *data, struct font_line_metrics **metrics)
|
|
{
|
|
d3d9_font_t *font = (d3d9_font_t*)data;
|
|
if (font && font->font_driver && font->font_data)
|
|
{
|
|
font->font_driver->get_line_metrics(font->font_data, metrics);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* VIDEO DRIVER
|
|
*/
|
|
|
|
/* Look up a shader uniform register from compiled bytecode CTAB.
|
|
* Returns the register index or -1 if not found. */
|
|
/* Set a float shader uniform by name. */
|
|
static INLINE void d3d9_hlsl_set_param_1f(
|
|
const DWORD *bytecode, size_t bytecode_dwords,
|
|
bool is_vertex,
|
|
LPDIRECT3DDEVICE9 dev, const char *name, const void *value)
|
|
{
|
|
int reg;
|
|
if (!bytecode || !bytecode_dwords || !name || !*name)
|
|
return;
|
|
reg = d3d9_hlsl_ctab_find_register(bytecode, bytecode_dwords, name, NULL, NULL);
|
|
if (reg >= 0)
|
|
{
|
|
float v4[4] = { *(const float*)value, 0.0f, 0.0f, 0.0f };
|
|
if (is_vertex)
|
|
d3d9_hlsl_set_vs_const(dev, reg, v4, 1);
|
|
else
|
|
d3d9_hlsl_set_ps_const(dev, reg, v4, 1);
|
|
}
|
|
}
|
|
|
|
/* Set a 4x4 matrix shader uniform by name. */
|
|
static bool d3d9_hlsl_load_program_from_file(
|
|
LPDIRECT3DDEVICE9 dev,
|
|
struct shader_pass *pass,
|
|
const char *prog)
|
|
{
|
|
D3DBlob code_f = NULL;
|
|
D3DBlob code_v = NULL;
|
|
wchar_t wpath[PATH_MAX_LENGTH];
|
|
|
|
if (!prog || !*prog)
|
|
return false;
|
|
|
|
/* Convert path to wide string for d3d_compile_from_file */
|
|
mbstowcs(wpath, prog, PATH_MAX_LENGTH);
|
|
|
|
if (!d3d_compile_from_file(wpath, "main_fragment", "ps_3_0", &code_f))
|
|
{
|
|
RARCH_ERR("[D3D9 HLSL] Could not compile fragment shader program (%s).\n", prog);
|
|
return false;
|
|
}
|
|
if (!d3d_compile_from_file(wpath, "main_vertex", "vs_3_0", &code_v))
|
|
{
|
|
RARCH_ERR("[D3D9 HLSL] Could not compile vertex shader program (%s).\n", prog);
|
|
code_f->lpVtbl->Release(code_f);
|
|
return false;
|
|
}
|
|
|
|
pass->ftable = NULL;
|
|
pass->vtable = NULL;
|
|
|
|
IDirect3DDevice9_CreatePixelShader(dev,
|
|
(const DWORD*)code_f->lpVtbl->GetBufferPointer(code_f),
|
|
(LPDIRECT3DPIXELSHADER9*)&pass->fprg);
|
|
IDirect3DDevice9_CreateVertexShader(dev,
|
|
(const DWORD*)code_v->lpVtbl->GetBufferPointer(code_v),
|
|
(LPDIRECT3DVERTEXSHADER9*)&pass->vprg);
|
|
code_f->lpVtbl->Release(code_f);
|
|
code_v->lpVtbl->Release(code_v);
|
|
|
|
return true;
|
|
}
|
|
|
|
static DWORD *d3d9_hlsl_dup_bytecode(D3DBlob blob, size_t *out_dwords);
|
|
|
|
static bool d3d9_hlsl_load_program_ex(
|
|
LPDIRECT3DDEVICE9 dev,
|
|
struct shader_pass *pass,
|
|
const char *prog,
|
|
hlsl_pass_data_t *pd)
|
|
{
|
|
D3DBlob code_f = NULL;
|
|
D3DBlob code_v = NULL;
|
|
size_t prog_len = strlen(prog);
|
|
|
|
if (!d3d_compile(prog, prog_len, "stock", "main_fragment", "ps_3_0", &code_f))
|
|
{
|
|
RARCH_ERR("[D3D9 HLSL] Could not compile stock fragment shader.\n");
|
|
return false;
|
|
}
|
|
if (!d3d_compile(prog, prog_len, "stock", "main_vertex", "vs_3_0", &code_v))
|
|
{
|
|
RARCH_ERR("[D3D9 HLSL] Could not compile stock vertex shader.\n");
|
|
code_f->lpVtbl->Release(code_f);
|
|
return false;
|
|
}
|
|
|
|
pass->ftable = NULL;
|
|
pass->vtable = NULL;
|
|
|
|
if (pd)
|
|
{
|
|
pd->ps_bytecode = d3d9_hlsl_dup_bytecode(code_f, &pd->ps_bytecode_dwords);
|
|
pd->vs_bytecode = d3d9_hlsl_dup_bytecode(code_v, &pd->vs_bytecode_dwords);
|
|
hlsl_uniform_map_from_bytecode(&pd->vs_map, pd->vs_bytecode, pd->vs_bytecode_dwords);
|
|
hlsl_uniform_map_from_bytecode(&pd->ps_map, pd->ps_bytecode, pd->ps_bytecode_dwords);
|
|
}
|
|
|
|
IDirect3DDevice9_CreatePixelShader(dev,
|
|
(const DWORD*)code_f->lpVtbl->GetBufferPointer(code_f),
|
|
(LPDIRECT3DPIXELSHADER9*)&pass->fprg);
|
|
IDirect3DDevice9_CreateVertexShader(dev,
|
|
(const DWORD*)code_v->lpVtbl->GetBufferPointer(code_v),
|
|
(LPDIRECT3DVERTEXSHADER9*)&pass->vprg);
|
|
code_f->lpVtbl->Release(code_f);
|
|
code_v->lpVtbl->Release(code_v);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool hlsl_d3d9_renderchain_init_shader_fvf(
|
|
d3d9_hlsl_renderchain_t *chain,
|
|
struct shader_pass *pass)
|
|
{
|
|
static const D3DVERTEXELEMENT9 decl[4] =
|
|
{
|
|
{0, offsetof(Vertex, x), D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,
|
|
D3DDECLUSAGE_POSITION, 0},
|
|
{0, offsetof(Vertex, color), D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT,
|
|
D3DDECLUSAGE_COLOR, 0},
|
|
{0, offsetof(Vertex, u), D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT,
|
|
D3DDECLUSAGE_TEXCOORD, 0},
|
|
D3DDECL_END()
|
|
};
|
|
|
|
return (SUCCEEDED(IDirect3DDevice9_CreateVertexDeclaration(chain->dev,
|
|
(const D3DVERTEXELEMENT9*)decl, (IDirect3DVertexDeclaration9**)&pass->vertex_decl)));
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
/* HLSL CTAB (constant table) bytecode dumper.
|
|
* Logs the contents of the CTAB embedded in compiled HLSL bytecode.
|
|
* Only active in debug builds; provided for shader bring-up and diagnostics. */
|
|
static void d3d9_hlsl_ctab_dump(const DWORD *bytecode, size_t bytecode_dwords,
|
|
const char *label)
|
|
{
|
|
size_t pos;
|
|
const DWORD CTAB_TAG = 0x42415443;
|
|
if (!bytecode || bytecode_dwords < 2) return;
|
|
for (pos = 1; pos < bytecode_dwords; )
|
|
{
|
|
DWORD token = bytecode[pos];
|
|
DWORD opcode = token & 0xFFFF;
|
|
if (opcode == 0xFFFF) break;
|
|
if (opcode == 0xFFFE)
|
|
{
|
|
DWORD comment_dwords = (token >> 16) & 0x7FFF;
|
|
if (pos + 1 + comment_dwords > bytecode_dwords) break;
|
|
if (comment_dwords >= 6 && bytecode[pos + 1] == CTAB_TAG)
|
|
{
|
|
const uint8_t *cs = (const uint8_t*)&bytecode[pos + 1];
|
|
size_t ctb = comment_dwords * sizeof(DWORD);
|
|
const uint8_t *hdr = cs + 4;
|
|
size_t ha = ctb - 4;
|
|
DWORD nc, cio, ci;
|
|
if (ha < 28) return;
|
|
nc = *(const DWORD*)(hdr + 12);
|
|
cio = *(const DWORD*)(hdr + 16);
|
|
RARCH_LOG("[D3D9 HLSL] CTAB %s (%u constants):\n", label, (unsigned)nc);
|
|
for (ci = 0; ci < nc; ci++)
|
|
{
|
|
size_t eo = cio + ci * 20;
|
|
if (eo + 20 > ha) break;
|
|
{
|
|
const uint8_t *e = hdr + eo;
|
|
DWORD no = *(const DWORD*)(e + 0);
|
|
WORD rs = *(const WORD*)(e + 4);
|
|
WORD ri = *(const WORD*)(e + 6);
|
|
WORD rc = *(const WORD*)(e + 8);
|
|
const char *cn = (no < ha) ? (const char*)(hdr + no) : "?";
|
|
RARCH_LOG(" [%u] '%s' set=%u reg=c%u cnt=%u\n",
|
|
ci, cn, (unsigned)rs, (unsigned)ri, (unsigned)rc);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
pos += 1 + comment_dwords; continue;
|
|
}
|
|
pos++;
|
|
}
|
|
}
|
|
#endif
|
|
static bool d3d9_hlsl_load_program_from_file_ex(
|
|
LPDIRECT3DDEVICE9 dev,
|
|
struct shader_pass *pass,
|
|
const char *prog,
|
|
hlsl_pass_data_t *pd);
|
|
|
|
static bool hlsl_d3d9_renderchain_create_first_pass(
|
|
LPDIRECT3DDEVICE9 dev,
|
|
hlsl_renderchain_t *hlsl_chain,
|
|
const struct LinkInfo *info,
|
|
unsigned _fmt)
|
|
{
|
|
int i;
|
|
struct shader_pass pass = { 0 };
|
|
d3d9_hlsl_renderchain_t *chain = &hlsl_chain->chain;
|
|
unsigned fmt =
|
|
(_fmt == RETRO_PIXEL_FORMAT_RGB565)
|
|
? D3D9_RGB565_FORMAT
|
|
: D3D9_XRGB8888_FORMAT;
|
|
|
|
pass.info = *info;
|
|
pass.last_width = 0;
|
|
pass.last_height = 0;
|
|
pass.attrib_map = (struct unsigned_vector_list*)
|
|
unsigned_vector_list_new();
|
|
|
|
chain->prev.ptr = 0;
|
|
|
|
{
|
|
int32_t filter = d3d_translate_filter(info->pass->filter);
|
|
for (i = 0; i < TEXTURES; i++)
|
|
{
|
|
chain->prev.last_width[i] = 0;
|
|
chain->prev.last_height[i] = 0;
|
|
chain->prev.vertex_buf[i] = NULL;
|
|
if (!SUCCEEDED(IDirect3DDevice9_CreateVertexBuffer(
|
|
chain->dev,
|
|
4 * sizeof(struct Vertex),
|
|
D3DUSAGE_WRITEONLY, 0,
|
|
D3DPOOL_DEFAULT,
|
|
(LPDIRECT3DVERTEXBUFFER9*)&chain->prev.vertex_buf[i], NULL)))
|
|
chain->prev.vertex_buf[i] = NULL;
|
|
|
|
if (!chain->prev.vertex_buf[i])
|
|
return false;
|
|
|
|
chain->prev.tex[i] = NULL;
|
|
IDirect3DDevice9_CreateTexture(chain->dev,
|
|
info->tex_w, info->tex_h, 1, 0,
|
|
(D3DFORMAT)fmt,
|
|
D3DPOOL_MANAGED,
|
|
(struct IDirect3DTexture9**)&chain->prev.tex[i], NULL);
|
|
|
|
if (!chain->prev.tex[i])
|
|
return false;
|
|
|
|
IDirect3DDevice9_SetTexture(chain->dev, 0, (IDirect3DBaseTexture9*)chain->prev.tex[i]);
|
|
IDirect3DDevice9_SetSamplerState(dev, 0, D3DSAMP_MINFILTER, filter);
|
|
IDirect3DDevice9_SetSamplerState(dev, 0, D3DSAMP_MAGFILTER, filter);
|
|
IDirect3DDevice9_SetSamplerState(dev, 0, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER);
|
|
IDirect3DDevice9_SetSamplerState(dev, 0, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER);
|
|
IDirect3DDevice9_SetTexture(chain->dev, 0, (IDirect3DBaseTexture9*)NULL);
|
|
}
|
|
}
|
|
|
|
/* Try to compile the per-pass shader. If the source path is empty
|
|
* (stock-shader mode), this returns false and pass.vprg/fprg stay NULL
|
|
* — exactly the same as before. */
|
|
hlsl_uniform_map_init(&hlsl_chain->pass_data[0].vs_map);
|
|
hlsl_uniform_map_init(&hlsl_chain->pass_data[0].ps_map);
|
|
hlsl_chain->pass_data[0].vs_bytecode = NULL;
|
|
hlsl_chain->pass_data[0].ps_bytecode = NULL;
|
|
d3d9_hlsl_load_program_from_file_ex(chain->dev,
|
|
&pass, info->pass->source.path,
|
|
&hlsl_chain->pass_data[0]);
|
|
hlsl_chain->pass_data_count = 1;
|
|
|
|
if (!hlsl_d3d9_renderchain_init_shader_fvf(chain, &pass))
|
|
return false;
|
|
shader_pass_vector_list_append(chain->passes, pass);
|
|
return true;
|
|
}
|
|
|
|
static void d3d9_hlsl_renderchain_render_pass(
|
|
d3d9_video_t *d3d,
|
|
hlsl_renderchain_t *chain,
|
|
struct shader_pass *pass,
|
|
unsigned pass_index,
|
|
unsigned width, unsigned height,
|
|
D3DVIEWPORT9 *vp,
|
|
unsigned rotation)
|
|
{
|
|
unsigned i;
|
|
const hlsl_pass_data_t *pd = NULL;
|
|
int32_t filter;
|
|
unsigned vp_width = vp->Width;
|
|
unsigned vp_height = vp->Height;
|
|
const float *mvp_data;
|
|
|
|
IDirect3DDevice9_SetViewport(chain->chain.dev, vp);
|
|
|
|
/* Compute MVP */
|
|
if (rotation == 0)
|
|
mvp_data = (const float*)&d3d->mvp;
|
|
else
|
|
{
|
|
/* Flat arrays matching register/column layout for
|
|
* SetVertexShaderConstantF + HLSL mul(matrix, vector).
|
|
* Each group of 4 = one register = one column.
|
|
* clip.x = c0.x*x + c1.x*y + c2.x*z + c3.x*w
|
|
* clip.y = c0.y*x + c1.y*y + c2.y*z + c3.y*w */
|
|
static const float rot90[16] = {
|
|
0, 2, 0, 0, -2, 0, 0, 0, 0, 0, 1, 0, 1,-1, 0, 1 };
|
|
static const float rot180[16] = {
|
|
-2, 0, 0, 0, 0,-2, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1 };
|
|
static const float rot270[16] = {
|
|
0,-2, 0, 0, 2, 0, 0, 0, 0, 0, 1, 0,-1, 1, 0, 1 };
|
|
switch (rotation)
|
|
{
|
|
case 1: mvp_data = rot90; break;
|
|
case 2: mvp_data = rot180; break;
|
|
case 3: mvp_data = rot270; break;
|
|
default: mvp_data = (const float*)&d3d->mvp; break;
|
|
}
|
|
}
|
|
|
|
/* === Set vertices === */
|
|
if (pass->last_width != width || pass->last_height != height)
|
|
{
|
|
struct Vertex vert[4];
|
|
void *verts = NULL;
|
|
float _u = (float)(width) / pass->info.tex_w;
|
|
float _v = (float)(height) / pass->info.tex_h;
|
|
|
|
pass->last_width = width;
|
|
pass->last_height = height;
|
|
|
|
vert[0].x = 0.0f;
|
|
vert[0].y = 1.0f;
|
|
vert[0].z = 1.0f;
|
|
|
|
vert[1].x = 1.0f;
|
|
vert[1].y = 1.0f;
|
|
vert[1].z = 1.0f;
|
|
|
|
vert[2].x = 0.0f;
|
|
vert[2].y = 0.0f;
|
|
vert[2].z = 1.0f;
|
|
|
|
vert[3].x = 1.0f;
|
|
vert[3].y = 0.0f;
|
|
vert[3].z = 1.0f;
|
|
|
|
vert[0].u = 0.0f;
|
|
vert[0].v = 0.0f;
|
|
vert[1].v = 0.0f;
|
|
vert[2].u = 0.0f;
|
|
vert[1].u = _u;
|
|
vert[2].v = _v;
|
|
vert[3].u = _u;
|
|
vert[3].v = _v;
|
|
|
|
vert[0].color = 0xFFFFFFFF;
|
|
vert[1].color = 0xFFFFFFFF;
|
|
vert[2].color = 0xFFFFFFFF;
|
|
vert[3].color = 0xFFFFFFFF;
|
|
|
|
IDirect3DVertexBuffer9_Lock(pass->vertex_buf, 0, 0, &verts, 0);
|
|
memcpy(verts, vert, sizeof(vert));
|
|
IDirect3DVertexBuffer9_Unlock(pass->vertex_buf);
|
|
}
|
|
|
|
/* Determine the pass data to use for uniform upload */
|
|
if (pass->vprg && pass->fprg
|
|
&& pass_index > 0 && pass_index <= chain->pass_data_count)
|
|
pd = &chain->pass_data[pass_index - 1];
|
|
|
|
/* === Render pass === */
|
|
filter = d3d_translate_filter(pass->info.pass->filter);
|
|
|
|
if (pd)
|
|
{
|
|
IDirect3DDevice9_SetVertexShader(chain->chain.dev,
|
|
(LPDIRECT3DVERTEXSHADER9)pass->vprg);
|
|
IDirect3DDevice9_SetPixelShader(chain->chain.dev,
|
|
(LPDIRECT3DPIXELSHADER9)pass->fprg);
|
|
}
|
|
else
|
|
{
|
|
IDirect3DDevice9_SetVertexShader(chain->chain.dev,
|
|
(LPDIRECT3DVERTEXSHADER9)(&chain->stock_shader)->vprg);
|
|
IDirect3DDevice9_SetPixelShader(chain->chain.dev,
|
|
(LPDIRECT3DPIXELSHADER9)(&chain->stock_shader)->fprg);
|
|
}
|
|
|
|
IDirect3DDevice9_SetTexture(chain->chain.dev, 0,
|
|
(IDirect3DBaseTexture9*)pass->tex);
|
|
|
|
/* Upload MVP to c0 (stock shader) */
|
|
IDirect3DDevice9_SetVertexShaderConstantF(chain->chain.dev,
|
|
0, mvp_data, 4);
|
|
|
|
IDirect3DDevice9_SetSamplerState(chain->chain.dev,
|
|
0, D3DSAMP_MINFILTER, filter);
|
|
IDirect3DDevice9_SetSamplerState(chain->chain.dev,
|
|
0, D3DSAMP_MAGFILTER, filter);
|
|
|
|
IDirect3DDevice9_SetVertexDeclaration(
|
|
chain->chain.dev, pass->vertex_decl);
|
|
IDirect3DDevice9_SetStreamSource(
|
|
chain->chain.dev, 0, pass->vertex_buf,
|
|
0, sizeof(struct Vertex));
|
|
|
|
/* Per-pass shader binding (only when we have compiled programs) */
|
|
if (pd)
|
|
{
|
|
if (pd->vs_map.mvp >= 0)
|
|
IDirect3DDevice9_SetVertexShaderConstantF(chain->chain.dev,
|
|
pd->vs_map.mvp, mvp_data, 4);
|
|
|
|
{
|
|
float video_size[2] = {
|
|
(float)pass->last_width, (float)pass->last_height };
|
|
float texture_size[2] = {
|
|
(float)pass->info.tex_w, (float)pass->info.tex_h };
|
|
float output_size[2] = {
|
|
(float)vp_width,
|
|
(float)vp_height };
|
|
float frame_cnt;
|
|
|
|
if (pass->info.pass->frame_count_mod)
|
|
frame_cnt = (float)(chain->chain.frame_count
|
|
% pass->info.pass->frame_count_mod);
|
|
else
|
|
/* SM2/SM3 has no uint registers, so frame_count must be
|
|
* bound as a float. fp32 mantissa is 23 bits, so values
|
|
* above 2^24 cannot be represented exactly. Mask to 24
|
|
* bits when the shader pass has not declared its own
|
|
* modulo, to keep the cast bit-exact in long sessions. */
|
|
frame_cnt = (float)(chain->chain.frame_count & 0xFFFFFFu);
|
|
|
|
if (pd->ps_map.video_size >= 0
|
|
&& pd->ps_map.video_size == pd->ps_map.texture_size)
|
|
{
|
|
float packed0[4] = { video_size[0], video_size[1],
|
|
texture_size[0], texture_size[1] };
|
|
float packed1[4] = { output_size[0], output_size[1],
|
|
frame_cnt, 1.0f };
|
|
d3d9_hlsl_set_ps_const(chain->chain.dev, pd->ps_map.video_size, packed0, 1);
|
|
d3d9_hlsl_set_ps_const(chain->chain.dev, pd->ps_map.output_size, packed1, 1);
|
|
}
|
|
else
|
|
{
|
|
float vs4[4] = { video_size[0], video_size[1], 0.0f, 0.0f };
|
|
float ts4[4] = { texture_size[0], texture_size[1], 0.0f, 0.0f };
|
|
d3d9_hlsl_set_ps_const(chain->chain.dev, pd->ps_map.video_size, vs4, 1);
|
|
d3d9_hlsl_set_ps_const(chain->chain.dev, pd->ps_map.texture_size, ts4, 1);
|
|
if (pd->ps_map.output_size >= 0
|
|
&& pd->ps_map.output_size == pd->ps_map.frame_count)
|
|
{
|
|
float packed[4] = { output_size[0], output_size[1], frame_cnt, 1.0f };
|
|
d3d9_hlsl_set_ps_const(chain->chain.dev, pd->ps_map.output_size, packed, 1);
|
|
}
|
|
else
|
|
{
|
|
float os4[4] = { output_size[0], output_size[1], 0.0f, 0.0f };
|
|
float fc4[4] = { frame_cnt, 0.0f, 0.0f, 0.0f };
|
|
d3d9_hlsl_set_ps_const(chain->chain.dev, pd->ps_map.output_size, os4, 1);
|
|
d3d9_hlsl_set_ps_const(chain->chain.dev, pd->ps_map.frame_count, fc4, 1);
|
|
}
|
|
}
|
|
|
|
/* Same for VS */
|
|
if (pd->vs_map.video_size >= 0
|
|
&& pd->vs_map.video_size == pd->vs_map.texture_size)
|
|
{
|
|
float packed0[4] = { video_size[0], video_size[1],
|
|
texture_size[0], texture_size[1] };
|
|
float packed1[4] = { output_size[0], output_size[1],
|
|
frame_cnt, 1.0f };
|
|
d3d9_hlsl_set_vs_const(chain->chain.dev, pd->vs_map.video_size, packed0, 1);
|
|
d3d9_hlsl_set_vs_const(chain->chain.dev, pd->vs_map.output_size, packed1, 1);
|
|
}
|
|
else
|
|
{
|
|
float vs4[4] = { video_size[0], video_size[1], 0.0f, 0.0f };
|
|
float ts4[4] = { texture_size[0], texture_size[1], 0.0f, 0.0f };
|
|
d3d9_hlsl_set_vs_const(chain->chain.dev, pd->vs_map.video_size, vs4, 1);
|
|
d3d9_hlsl_set_vs_const(chain->chain.dev, pd->vs_map.texture_size, ts4, 1);
|
|
if (pd->vs_map.output_size >= 0
|
|
&& pd->vs_map.output_size == pd->vs_map.frame_count)
|
|
{
|
|
float packed[4] = { output_size[0], output_size[1], frame_cnt, 1.0f };
|
|
d3d9_hlsl_set_vs_const(chain->chain.dev, pd->vs_map.output_size, packed, 1);
|
|
}
|
|
else
|
|
{
|
|
float os4[4] = { output_size[0], output_size[1], 0.0f, 0.0f };
|
|
float fc4[4] = { frame_cnt, 0.0f, 0.0f, 0.0f };
|
|
d3d9_hlsl_set_vs_const(chain->chain.dev, pd->vs_map.output_size, os4, 1);
|
|
d3d9_hlsl_set_vs_const(chain->chain.dev, pd->vs_map.frame_count, fc4, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Bind ORIG texture */
|
|
{
|
|
struct shader_pass *first_pass = (struct shader_pass*)
|
|
&chain->chain.passes->data[0];
|
|
int ps_idx = pd->ps_map.orig_texture;
|
|
|
|
if (ps_idx >= 0)
|
|
{
|
|
int32_t orig_filter = d3d_translate_filter(
|
|
first_pass->info.pass->filter);
|
|
IDirect3DDevice9_SetTexture(chain->chain.dev,
|
|
ps_idx, (IDirect3DBaseTexture9*)first_pass->tex);
|
|
IDirect3DDevice9_SetSamplerState(chain->chain.dev,
|
|
ps_idx, D3DSAMP_MINFILTER, orig_filter);
|
|
IDirect3DDevice9_SetSamplerState(chain->chain.dev,
|
|
ps_idx, D3DSAMP_MAGFILTER, orig_filter);
|
|
IDirect3DDevice9_SetSamplerState(chain->chain.dev,
|
|
ps_idx, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER);
|
|
IDirect3DDevice9_SetSamplerState(chain->chain.dev,
|
|
ps_idx, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER);
|
|
unsigned_vector_list_append(chain->chain.bound_tex, ps_idx);
|
|
}
|
|
|
|
{
|
|
float vs[4] = { (float)first_pass->last_width,
|
|
(float)first_pass->last_height, 0.0f, 0.0f };
|
|
float ts[4] = { (float)first_pass->info.tex_w,
|
|
(float)first_pass->info.tex_h, 0.0f, 0.0f };
|
|
d3d9_hlsl_set_vs_const(chain->chain.dev, pd->vs_map.orig_video_size, vs, 1);
|
|
d3d9_hlsl_set_vs_const(chain->chain.dev, pd->vs_map.orig_texture_size, ts, 1);
|
|
d3d9_hlsl_set_ps_const(chain->chain.dev, pd->ps_map.orig_video_size, vs, 1);
|
|
d3d9_hlsl_set_ps_const(chain->chain.dev, pd->ps_map.orig_texture_size, ts, 1);
|
|
}
|
|
}
|
|
|
|
/* Bind PREV textures */
|
|
{
|
|
int32_t prev_filter = d3d_translate_filter(
|
|
chain->chain.passes->data[0].info.pass->filter);
|
|
float ts[4] = { (float)chain->chain.passes->data[0].info.tex_w,
|
|
(float)chain->chain.passes->data[0].info.tex_h, 0.0f, 0.0f };
|
|
|
|
for (i = 0; i < TEXTURES - 1; i++)
|
|
{
|
|
int ps_idx = pd->ps_map.prev_texture[i];
|
|
if (ps_idx >= 0)
|
|
{
|
|
LPDIRECT3DTEXTURE9 tex = chain->chain.prev.tex[
|
|
(chain->chain.prev.ptr - (i + 1)) & TEXTURESMASK];
|
|
IDirect3DDevice9_SetTexture(chain->chain.dev,
|
|
ps_idx, (IDirect3DBaseTexture9*)tex);
|
|
IDirect3DDevice9_SetSamplerState(chain->chain.dev,
|
|
ps_idx, D3DSAMP_MINFILTER, prev_filter);
|
|
IDirect3DDevice9_SetSamplerState(chain->chain.dev,
|
|
ps_idx, D3DSAMP_MAGFILTER, prev_filter);
|
|
IDirect3DDevice9_SetSamplerState(chain->chain.dev,
|
|
ps_idx, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER);
|
|
IDirect3DDevice9_SetSamplerState(chain->chain.dev,
|
|
ps_idx, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER);
|
|
unsigned_vector_list_append(chain->chain.bound_tex, ps_idx);
|
|
}
|
|
|
|
{
|
|
float vs[4] = {
|
|
(float)chain->chain.prev.last_width[
|
|
(chain->chain.prev.ptr - (i + 1)) & TEXTURESMASK],
|
|
(float)chain->chain.prev.last_height[
|
|
(chain->chain.prev.ptr - (i + 1)) & TEXTURESMASK],
|
|
0.0f, 0.0f };
|
|
d3d9_hlsl_set_vs_const(chain->chain.dev, pd->vs_map.prev_video_size[i], vs, 1);
|
|
d3d9_hlsl_set_vs_const(chain->chain.dev, pd->vs_map.prev_texture_size[i], ts, 1);
|
|
d3d9_hlsl_set_ps_const(chain->chain.dev, pd->ps_map.prev_video_size[i], vs, 1);
|
|
d3d9_hlsl_set_ps_const(chain->chain.dev, pd->ps_map.prev_texture_size[i], ts, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Bind LUT textures */
|
|
for (i = 0; i < chain->chain.luts->count; i++)
|
|
{
|
|
int ps_idx = d3d9_hlsl_ctab_find_register(
|
|
pd->ps_bytecode, pd->ps_bytecode_dwords,
|
|
chain->chain.luts->data[i].id, NULL, NULL);
|
|
if (ps_idx >= 0)
|
|
{
|
|
int32_t lut_filter = chain->chain.luts->data[i].smooth ? 2 : 1;
|
|
IDirect3DDevice9_SetTexture(chain->chain.dev, ps_idx, (IDirect3DBaseTexture9*)chain->chain.luts->data[i].tex);
|
|
IDirect3DDevice9_SetSamplerState(chain->chain.dev, ps_idx, D3DSAMP_MAGFILTER, lut_filter);
|
|
IDirect3DDevice9_SetSamplerState(chain->chain.dev, ps_idx, D3DSAMP_MINFILTER, lut_filter);
|
|
IDirect3DDevice9_SetSamplerState(chain->chain.dev, ps_idx, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER);
|
|
IDirect3DDevice9_SetSamplerState(chain->chain.dev, ps_idx, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER);
|
|
unsigned_vector_list_append(chain->chain.bound_tex, ps_idx);
|
|
}
|
|
}
|
|
|
|
/* Bind intermediate PASSn textures (2+ indices behind) */
|
|
if (pass_index >= 3)
|
|
{
|
|
for (i = 1; i < pass_index - 1; i++)
|
|
{
|
|
struct shader_pass *cp = (struct shader_pass*)
|
|
&chain->chain.passes->data[i];
|
|
int ps_idx = pd->ps_map.pass_texture[i - 1];
|
|
|
|
if (ps_idx >= 0)
|
|
{
|
|
int32_t pf = d3d_translate_filter(cp->info.pass->filter);
|
|
IDirect3DDevice9_SetTexture(chain->chain.dev,
|
|
ps_idx, (IDirect3DBaseTexture9*)cp->tex);
|
|
IDirect3DDevice9_SetSamplerState(chain->chain.dev,
|
|
ps_idx, D3DSAMP_MINFILTER, pf);
|
|
IDirect3DDevice9_SetSamplerState(chain->chain.dev,
|
|
ps_idx, D3DSAMP_MAGFILTER, pf);
|
|
IDirect3DDevice9_SetSamplerState(chain->chain.dev,
|
|
ps_idx, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER);
|
|
IDirect3DDevice9_SetSamplerState(chain->chain.dev,
|
|
ps_idx, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER);
|
|
unsigned_vector_list_append(chain->chain.bound_tex, ps_idx);
|
|
}
|
|
|
|
{
|
|
float vs[4] = { (float)cp->last_width,
|
|
(float)cp->last_height, 0.0f, 0.0f };
|
|
float ts[4] = { (float)cp->info.tex_w,
|
|
(float)cp->info.tex_h, 0.0f, 0.0f };
|
|
d3d9_hlsl_set_vs_const(chain->chain.dev, pd->vs_map.pass_video_size[i-1], vs, 1);
|
|
d3d9_hlsl_set_vs_const(chain->chain.dev, pd->vs_map.pass_texture_size[i-1], ts, 1);
|
|
d3d9_hlsl_set_ps_const(chain->chain.dev, pd->ps_map.pass_video_size[i-1], vs, 1);
|
|
d3d9_hlsl_set_ps_const(chain->chain.dev, pd->ps_map.pass_texture_size[i-1], ts, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Upload shader parameters */
|
|
for (i = 0; i < d3d->shader.num_parameters; i++)
|
|
{
|
|
float val[4] = { d3d->shader.parameters[i].current, 0.0f, 0.0f, 0.0f };
|
|
int ps_reg = d3d9_hlsl_ctab_find_register(
|
|
pd->ps_bytecode, pd->ps_bytecode_dwords,
|
|
d3d->shader.parameters[i].id, NULL, NULL);
|
|
int vs_reg = d3d9_hlsl_ctab_find_register(
|
|
pd->vs_bytecode, pd->vs_bytecode_dwords,
|
|
d3d->shader.parameters[i].id, NULL, NULL);
|
|
d3d9_hlsl_set_ps_const(chain->chain.dev, ps_reg, val, 1);
|
|
d3d9_hlsl_set_vs_const(chain->chain.dev, vs_reg, val, 1);
|
|
}
|
|
}
|
|
|
|
IDirect3DDevice9_BeginScene(chain->chain.dev);
|
|
IDirect3DDevice9_DrawPrimitive(chain->chain.dev,
|
|
D3DPT_TRIANGLESTRIP, 0, 2);
|
|
IDirect3DDevice9_EndScene(chain->chain.dev);
|
|
|
|
/* So we don't render with linear filter into render targets,
|
|
* which apparently looked odd (too blurry). */
|
|
IDirect3DDevice9_SetSamplerState(chain->chain.dev,
|
|
0, D3DSAMP_MINFILTER, D3DTEXF_POINT);
|
|
IDirect3DDevice9_SetSamplerState(chain->chain.dev,
|
|
0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
|
|
|
|
/* Unbind all bound textures and vertex streams. */
|
|
{
|
|
int j;
|
|
for (j = 0; j < (int) chain->chain.bound_tex->count; j++)
|
|
{
|
|
IDirect3DDevice9_SetSamplerState(chain->chain.dev,
|
|
chain->chain.bound_tex->data[j], D3DSAMP_MINFILTER, D3DTEXF_POINT);
|
|
IDirect3DDevice9_SetSamplerState(chain->chain.dev,
|
|
chain->chain.bound_tex->data[j], D3DSAMP_MAGFILTER, D3DTEXF_POINT);
|
|
IDirect3DDevice9_SetTexture(chain->chain.dev,
|
|
chain->chain.bound_tex->data[j], (IDirect3DBaseTexture9*)NULL);
|
|
}
|
|
|
|
for (j = 0; j < (int) chain->chain.bound_vert->count; j++)
|
|
IDirect3DDevice9_SetStreamSource(chain->chain.dev, chain->chain.bound_vert->data[j], 0, 0, 0);
|
|
}
|
|
|
|
if (chain->chain.bound_tex)
|
|
chain->chain.bound_tex->count = 0;
|
|
|
|
if (chain->chain.bound_vert)
|
|
chain->chain.bound_vert->count = 0;
|
|
}
|
|
|
|
static void d3d9_hlsl_deinit_progs(hlsl_renderchain_t *chain)
|
|
{
|
|
if (chain->chain.passes->count >= 1)
|
|
{
|
|
size_t i;
|
|
|
|
d3d9_vertex_buffer_free(NULL,
|
|
chain->chain.passes->data[0].vertex_decl);
|
|
|
|
for (i = 1; i < chain->chain.passes->count; i++)
|
|
{
|
|
if (chain->chain.passes->data[i].tex)
|
|
IDirect3DTexture9_Release(chain->chain.passes->data[i].tex);
|
|
chain->chain.passes->data[i].tex = NULL;
|
|
d3d9_vertex_buffer_free(
|
|
chain->chain.passes->data[i].vertex_buf,
|
|
chain->chain.passes->data[i].vertex_decl);
|
|
}
|
|
}
|
|
|
|
/* Free per-pass bytecodes */
|
|
{
|
|
unsigned i;
|
|
for (i = 0; i < chain->pass_data_count; i++)
|
|
hlsl_pass_data_free(&chain->pass_data[i]);
|
|
chain->pass_data_count = 0;
|
|
}
|
|
hlsl_pass_data_free(&chain->stock_data);
|
|
}
|
|
|
|
static void hlsl_d3d9_renderchain_free(void *data)
|
|
{
|
|
hlsl_renderchain_t *chain = (hlsl_renderchain_t*)data;
|
|
|
|
if (!chain)
|
|
return;
|
|
|
|
/* Destroy resources */
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < TEXTURES; i++)
|
|
{
|
|
if (chain->chain.prev.tex[i])
|
|
IDirect3DTexture9_Release(chain->chain.prev.tex[i]);
|
|
if (chain->chain.prev.vertex_buf[i])
|
|
d3d9_vertex_buffer_free(chain->chain.prev.vertex_buf[i], NULL);
|
|
}
|
|
|
|
d3d9_hlsl_deinit_progs(chain);
|
|
|
|
for (i = 0; i < chain->chain.luts->count; i++)
|
|
{
|
|
if (chain->chain.luts->data[i].tex)
|
|
IDirect3DTexture9_Release(chain->chain.luts->data[i].tex);
|
|
}
|
|
}
|
|
|
|
/* Destroy passes and luts */
|
|
if (chain->chain.passes)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < (int) chain->chain.passes->count; i++)
|
|
{
|
|
if (chain->chain.passes->data[i].attrib_map)
|
|
free(chain->chain.passes->data[i].attrib_map);
|
|
}
|
|
|
|
shader_pass_vector_list_free(chain->chain.passes);
|
|
}
|
|
|
|
lut_info_vector_list_free(chain->chain.luts);
|
|
unsigned_vector_list_free(chain->chain.bound_tex);
|
|
unsigned_vector_list_free(chain->chain.bound_vert);
|
|
|
|
free(chain);
|
|
}
|
|
|
|
static bool hlsl_d3d9_renderchain_init(
|
|
d3d9_video_t *d3d,
|
|
hlsl_renderchain_t *chain,
|
|
LPDIRECT3DDEVICE9 dev,
|
|
const D3DVIEWPORT9 *out_vp,
|
|
const struct LinkInfo *info,
|
|
unsigned fmt
|
|
)
|
|
{
|
|
chain->chain.dev = dev;
|
|
chain->chain.out_vp = (D3DVIEWPORT9*)out_vp;
|
|
chain->chain.frame_count = 0;
|
|
chain->chain.pixel_size = (fmt == RETRO_PIXEL_FORMAT_RGB565) ? 2 : 4;
|
|
|
|
if (!hlsl_d3d9_renderchain_create_first_pass(dev, chain, info, fmt))
|
|
return false;
|
|
|
|
hlsl_uniform_map_init(&chain->stock_data.vs_map);
|
|
hlsl_uniform_map_init(&chain->stock_data.ps_map);
|
|
chain->stock_data.vs_bytecode = NULL;
|
|
chain->stock_data.ps_bytecode = NULL;
|
|
if (!d3d9_hlsl_load_program_ex(chain->chain.dev, &chain->stock_shader,
|
|
stock_hlsl_program, &chain->stock_data))
|
|
return false;
|
|
|
|
IDirect3DDevice9_SetVertexShader(dev, (LPDIRECT3DVERTEXSHADER9)(&chain->stock_shader)->vprg);
|
|
IDirect3DDevice9_SetPixelShader(dev, (LPDIRECT3DPIXELSHADER9)(&chain->stock_shader)->fprg);
|
|
|
|
/* Compile XMB pipeline shaders */
|
|
{
|
|
const char *pipeline_sources[6];
|
|
unsigned i;
|
|
pipeline_sources[0] = hlsl_ribbon_program;
|
|
pipeline_sources[1] = hlsl_ribbon_simple_program;
|
|
pipeline_sources[2] = hlsl_simple_snow_program;
|
|
pipeline_sources[3] = hlsl_snow_program;
|
|
pipeline_sources[4] = hlsl_bokeh_program;
|
|
pipeline_sources[5] = hlsl_snowflake_program;
|
|
chain->pipeline_inited = true;
|
|
for (i = 0; i < 6; i++)
|
|
{
|
|
hlsl_uniform_map_init(&chain->pipeline_data[i].vs_map);
|
|
hlsl_uniform_map_init(&chain->pipeline_data[i].ps_map);
|
|
chain->pipeline_data[i].vs_bytecode = NULL;
|
|
chain->pipeline_data[i].ps_bytecode = NULL;
|
|
if (!d3d9_hlsl_load_program_ex(dev,
|
|
&chain->pipeline_shaders[i],
|
|
pipeline_sources[i],
|
|
&chain->pipeline_data[i]))
|
|
{
|
|
RARCH_WARN("[D3D9 HLSL] Could not compile XMB pipeline shader %u, effects disabled.\n", i);
|
|
chain->pipeline_inited = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void hlsl_d3d9_renderchain_render(
|
|
d3d9_video_t *d3d,
|
|
const void *frame,
|
|
unsigned width, unsigned height,
|
|
unsigned pitch, unsigned rotation)
|
|
{
|
|
LPDIRECT3DSURFACE9 back_buffer = NULL;
|
|
LPDIRECT3DSURFACE9 target;
|
|
unsigned i, current_width, current_height,
|
|
out_width = 0, out_height = 0;
|
|
struct shader_pass *last_pass = NULL;
|
|
struct shader_pass *first_pass = NULL;
|
|
hlsl_renderchain_t *chain = (hlsl_renderchain_t*)
|
|
d3d->renderchain_data;
|
|
|
|
chain->chain.passes->data[0].tex = chain->chain.prev.tex[
|
|
chain->chain.prev.ptr];
|
|
chain->chain.passes->data[0].vertex_buf = chain->chain.prev.vertex_buf[
|
|
chain->chain.prev.ptr];
|
|
chain->chain.passes->data[0].last_width = chain->chain.prev.last_width[
|
|
chain->chain.prev.ptr];
|
|
chain->chain.passes->data[0].last_height = chain->chain.prev.last_height[
|
|
chain->chain.prev.ptr];
|
|
|
|
current_width = width;
|
|
current_height = height;
|
|
|
|
first_pass = (struct shader_pass*)
|
|
&chain->chain.passes->data[0];
|
|
|
|
/* Blit frame to first pass texture */
|
|
{
|
|
unsigned y;
|
|
D3DLOCKED_RECT d3dlr;
|
|
d3dlr.Pitch = 0;
|
|
d3dlr.pBits = NULL;
|
|
|
|
IDirect3DTexture9_LockRect(first_pass->tex, 0, &d3dlr, NULL, 0);
|
|
|
|
if (first_pass->last_width != width || first_pass->last_height != height)
|
|
memset(d3dlr.pBits, 0, first_pass->info.tex_h * d3dlr.Pitch);
|
|
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
const uint8_t *in = (const uint8_t*)frame + y * pitch;
|
|
uint8_t *out = (uint8_t*)d3dlr.pBits + y * d3dlr.Pitch;
|
|
memcpy(out, in, width * chain->chain.pixel_size);
|
|
}
|
|
IDirect3DTexture9_UnlockRect(first_pass->tex, 0);
|
|
}
|
|
|
|
/* Grab back buffer. */
|
|
if (chain->chain.dev)
|
|
{
|
|
if (!SUCCEEDED(IDirect3DDevice9_GetRenderTarget(chain->chain.dev,
|
|
0, (LPDIRECT3DSURFACE9*)&back_buffer)))
|
|
back_buffer = NULL;
|
|
}
|
|
|
|
/* In-between render target passes. */
|
|
for (i = 0; i < chain->chain.passes->count - 1; i++)
|
|
{
|
|
D3DVIEWPORT9 viewport = {0};
|
|
struct shader_pass *from_pass = (struct shader_pass*)
|
|
&chain->chain.passes->data[i];
|
|
struct shader_pass *to_pass = (struct shader_pass*)
|
|
&chain->chain.passes->data[i + 1];
|
|
|
|
IDirect3DTexture9_GetSurfaceLevel(
|
|
(LPDIRECT3DTEXTURE9)to_pass->tex, 0, (IDirect3DSurface9**)&target);
|
|
IDirect3DDevice9_SetRenderTarget(chain->chain.dev, 0, target);
|
|
|
|
switch (from_pass->info.pass->fbo.type_x)
|
|
{
|
|
case RARCH_SCALE_VIEWPORT:
|
|
out_width = from_pass->info.pass->fbo.scale_x * chain->chain.out_vp->Width;
|
|
break;
|
|
case RARCH_SCALE_ABSOLUTE:
|
|
out_width = from_pass->info.pass->fbo.abs_x;
|
|
break;
|
|
case RARCH_SCALE_INPUT:
|
|
out_width = from_pass->info.pass->fbo.scale_x * current_width;
|
|
break;
|
|
}
|
|
switch (from_pass->info.pass->fbo.type_y)
|
|
{
|
|
case RARCH_SCALE_VIEWPORT:
|
|
out_height = from_pass->info.pass->fbo.scale_y * chain->chain.out_vp->Height;
|
|
break;
|
|
case RARCH_SCALE_ABSOLUTE:
|
|
out_height = from_pass->info.pass->fbo.abs_y;
|
|
break;
|
|
case RARCH_SCALE_INPUT:
|
|
out_height = from_pass->info.pass->fbo.scale_y * current_height;
|
|
break;
|
|
}
|
|
|
|
/* Clear out whole FBO. */
|
|
viewport.Width = to_pass->info.tex_w;
|
|
viewport.Height = to_pass->info.tex_h;
|
|
viewport.MinZ = 0.0f;
|
|
viewport.MaxZ = 1.0f;
|
|
|
|
IDirect3DDevice9_SetViewport(
|
|
chain->chain.dev, (D3DVIEWPORT9*)&viewport);
|
|
IDirect3DDevice9_Clear(
|
|
chain->chain.dev, 0, 0, D3DCLEAR_TARGET,
|
|
0, 1, 0);
|
|
|
|
viewport.Width = out_width;
|
|
viewport.Height = out_height;
|
|
|
|
d3d9_hlsl_renderchain_render_pass(
|
|
d3d,
|
|
chain, from_pass,
|
|
i + 1,
|
|
current_width, current_height,
|
|
&viewport, 0);
|
|
|
|
current_width = out_width;
|
|
current_height = out_height;
|
|
IDirect3DSurface9_Release(target);
|
|
}
|
|
|
|
/* Final pass */
|
|
IDirect3DDevice9_SetRenderTarget(chain->chain.dev, 0, back_buffer);
|
|
|
|
last_pass = (struct shader_pass*)&chain->chain.passes->
|
|
data[chain->chain.passes->count - 1];
|
|
|
|
switch (last_pass->info.pass->fbo.type_x)
|
|
{
|
|
case RARCH_SCALE_VIEWPORT:
|
|
out_width = last_pass->info.pass->fbo.scale_x * chain->chain.out_vp->Width;
|
|
break;
|
|
case RARCH_SCALE_ABSOLUTE:
|
|
out_width = last_pass->info.pass->fbo.abs_x;
|
|
break;
|
|
case RARCH_SCALE_INPUT:
|
|
out_width = last_pass->info.pass->fbo.scale_x * current_width;
|
|
break;
|
|
}
|
|
switch (last_pass->info.pass->fbo.type_y)
|
|
{
|
|
case RARCH_SCALE_VIEWPORT:
|
|
out_height = last_pass->info.pass->fbo.scale_y * chain->chain.out_vp->Height;
|
|
break;
|
|
case RARCH_SCALE_ABSOLUTE:
|
|
out_height = last_pass->info.pass->fbo.abs_y;
|
|
break;
|
|
case RARCH_SCALE_INPUT:
|
|
out_height = last_pass->info.pass->fbo.scale_y * current_height;
|
|
break;
|
|
}
|
|
|
|
d3d9_hlsl_renderchain_render_pass(
|
|
d3d,
|
|
chain, last_pass,
|
|
chain->chain.passes->count,
|
|
current_width, current_height,
|
|
chain->chain.out_vp, rotation);
|
|
|
|
chain->chain.frame_count++;
|
|
|
|
if (back_buffer)
|
|
IDirect3DSurface9_Release(back_buffer);
|
|
|
|
chain->chain.prev.last_width[chain->chain.prev.ptr] = chain->chain.passes->data[0].last_width;
|
|
chain->chain.prev.last_height[chain->chain.prev.ptr] = chain->chain.passes->data[0].last_height;
|
|
chain->chain.prev.ptr = (chain->chain.prev.ptr + 1) & TEXTURESMASK;
|
|
|
|
IDirect3DDevice9_SetVertexShader(chain->chain.dev, (LPDIRECT3DVERTEXSHADER9)(&chain->stock_shader)->vprg);
|
|
IDirect3DDevice9_SetPixelShader(chain->chain.dev, (LPDIRECT3DPIXELSHADER9)(&chain->stock_shader)->fprg);
|
|
|
|
if (chain->stock_data.vs_map.mvp >= 0)
|
|
IDirect3DDevice9_SetVertexShaderConstantF(chain->chain.dev,
|
|
chain->stock_data.vs_map.mvp, (const float*)&d3d->mvp, 4);
|
|
}
|
|
|
|
static bool hlsl_d3d9_renderchain_add_pass(
|
|
hlsl_renderchain_t *chain,
|
|
const struct LinkInfo *info)
|
|
{
|
|
struct shader_pass pass;
|
|
LPDIRECT3DTEXTURE9 tex;
|
|
LPDIRECT3DVERTEXBUFFER9 vertbuf = NULL;
|
|
|
|
pass.info = *info;
|
|
pass.last_width = 0;
|
|
pass.last_height = 0;
|
|
pass.attrib_map = (struct unsigned_vector_list*)
|
|
unsigned_vector_list_new();
|
|
pass.pool = D3DPOOL_DEFAULT;
|
|
|
|
{
|
|
unsigned idx = chain->pass_data_count;
|
|
if (idx < GFX_MAX_SHADERS + 1)
|
|
{
|
|
hlsl_uniform_map_init(&chain->pass_data[idx].vs_map);
|
|
hlsl_uniform_map_init(&chain->pass_data[idx].ps_map);
|
|
chain->pass_data[idx].vs_bytecode = NULL;
|
|
chain->pass_data[idx].ps_bytecode = NULL;
|
|
d3d9_hlsl_load_program_from_file_ex(
|
|
chain->chain.dev, &pass, info->pass->source.path,
|
|
&chain->pass_data[idx]);
|
|
chain->pass_data_count++;
|
|
}
|
|
else
|
|
d3d9_hlsl_load_program_from_file(
|
|
chain->chain.dev, &pass, info->pass->source.path);
|
|
}
|
|
|
|
if (!hlsl_d3d9_renderchain_init_shader_fvf(&chain->chain, &pass))
|
|
return false;
|
|
|
|
if (!SUCCEEDED(IDirect3DDevice9_CreateVertexBuffer(
|
|
chain->chain.dev,
|
|
4 * sizeof(struct Vertex),
|
|
D3DUSAGE_WRITEONLY, 0,
|
|
D3DPOOL_DEFAULT,
|
|
(LPDIRECT3DVERTEXBUFFER9*)&vertbuf, NULL)))
|
|
return false;
|
|
|
|
pass.vertex_buf = vertbuf;
|
|
|
|
tex = NULL;
|
|
IDirect3DDevice9_CreateTexture(chain->chain.dev,
|
|
info->tex_w,
|
|
info->tex_h,
|
|
1,
|
|
D3DUSAGE_RENDERTARGET,
|
|
(chain->chain.passes->data[
|
|
chain->chain.passes->count - 1].info.pass->fbo.flags & FBO_SCALE_FLAG_FP_FBO)
|
|
? D3DFMT_A32B32G32R32F : D3D9_ARGB8888_FORMAT,
|
|
D3DPOOL_DEFAULT,
|
|
(struct IDirect3DTexture9**)&tex, NULL);
|
|
|
|
if (!tex)
|
|
return false;
|
|
|
|
pass.tex = tex;
|
|
|
|
IDirect3DDevice9_SetTexture(chain->chain.dev, 0, (IDirect3DBaseTexture9*)pass.tex);
|
|
IDirect3DDevice9_SetSamplerState(chain->chain.dev, 0, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER);
|
|
IDirect3DDevice9_SetSamplerState(chain->chain.dev, 0, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER);
|
|
IDirect3DDevice9_SetTexture(chain->chain.dev, 0, (IDirect3DBaseTexture9*)NULL);
|
|
|
|
shader_pass_vector_list_append(chain->chain.passes, pass);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* =====================================================================
|
|
* CTAB-aware shader binding for multi-pass shader presets.
|
|
*
|
|
* These functions are used when a shader preset is loaded and per-pass
|
|
* programs are available. They use the CTAB uniform maps to set
|
|
* parameters via SetVertexShaderConstantF / SetPixelShaderConstantF
|
|
* and bind ORIG/PREV/PASS/LUT textures at the correct sampler slots.
|
|
*
|
|
* The stock-shader path does NOT use these functions — it continues
|
|
* to use the existing no-op stubs, preserving identical behaviour.
|
|
* ===================================================================== */
|
|
|
|
static DWORD *d3d9_hlsl_dup_bytecode(D3DBlob blob, size_t *out_dwords)
|
|
{
|
|
size_t byte_size = blob->lpVtbl->GetBufferSize(blob);
|
|
size_t dwords = byte_size / sizeof(DWORD);
|
|
DWORD *buf = (DWORD*)malloc(byte_size);
|
|
if (buf)
|
|
{
|
|
memcpy(buf, blob->lpVtbl->GetBufferPointer(blob), byte_size);
|
|
*out_dwords = dwords;
|
|
}
|
|
else
|
|
*out_dwords = 0;
|
|
return buf;
|
|
}
|
|
|
|
/* Read a file into a malloc'd null-terminated string. Caller frees. */
|
|
static char *d3d9_hlsl_read_file(const char *path, size_t *out_len)
|
|
{
|
|
int64_t len;
|
|
char *buf = NULL;
|
|
RFILE *fp = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ,
|
|
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
|
if (!fp)
|
|
return NULL;
|
|
|
|
len = filestream_get_size(fp);
|
|
if (len <= 0)
|
|
{
|
|
filestream_close(fp);
|
|
return NULL;
|
|
}
|
|
|
|
buf = (char*)malloc((size_t)len + 1);
|
|
if (!buf)
|
|
{
|
|
filestream_close(fp);
|
|
return NULL;
|
|
}
|
|
|
|
filestream_read(fp, buf, (size_t)len);
|
|
buf[len] = '\0';
|
|
filestream_close(fp);
|
|
|
|
if (out_len)
|
|
*out_len = (size_t)len;
|
|
return buf;
|
|
}
|
|
|
|
/* Simple #include preprocessor for Cg/HLSL shader files.
|
|
*
|
|
* Scans the source for lines matching:
|
|
* #include "filename"
|
|
* and replaces them with the contents of the referenced file,
|
|
* resolved relative to base_dir. Handles recursive includes
|
|
* up to 16 levels deep. Returns a malloc'd string. */
|
|
/* Simple preprocessor state for #ifdef/#ifndef/#else/#endif.
|
|
* Supports nesting up to 32 levels deep. */
|
|
#define PREPROC_MAX_DEPTH 32
|
|
|
|
typedef struct {
|
|
const char *names[64];
|
|
const char *values[64]; /* NULL = defined but no value (flag only) */
|
|
unsigned count;
|
|
} preproc_defines_t;
|
|
|
|
static void preproc_defines_init(preproc_defines_t *d)
|
|
{
|
|
unsigned i;
|
|
for (i = 0; i < d->count; i++)
|
|
{
|
|
free((void*)d->names[i]);
|
|
d->names[i] = NULL;
|
|
d->values[i] = NULL;
|
|
}
|
|
d->count = 0;
|
|
}
|
|
|
|
static bool preproc_is_defined(const preproc_defines_t *d, const char *name, size_t name_len)
|
|
{
|
|
unsigned i;
|
|
for (i = 0; i < d->count; i++)
|
|
if (strlen(d->names[i]) == name_len && strncmp(d->names[i], name, name_len) == 0)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
|
|
static void preproc_add_define(preproc_defines_t *d, const char *name, const char *value)
|
|
{
|
|
if (d->count < 64)
|
|
{
|
|
d->names[d->count] = name;
|
|
d->values[d->count] = value;
|
|
d->count++;
|
|
}
|
|
}
|
|
|
|
static bool d3d9_hlsl_is_ident_char(char c);
|
|
|
|
static preproc_defines_t s_defines = { {NULL}, {NULL}, 0 };
|
|
|
|
static char *d3d9_hlsl_preprocess_includes(
|
|
const char *source, const char *base_dir, unsigned depth)
|
|
{
|
|
size_t src_len = strlen(source);
|
|
size_t cap = src_len * 2 + 1;
|
|
size_t pos = 0;
|
|
char *out = (char*)malloc(cap);
|
|
const char *p = source;
|
|
|
|
/* Conditional compilation stack */
|
|
bool cond_active[PREPROC_MAX_DEPTH]; /* is this level's branch active? */
|
|
bool cond_done[PREPROC_MAX_DEPTH]; /* has any branch been taken? */
|
|
int cond_depth = 0;
|
|
bool output_enabled = true;
|
|
|
|
/* Reset defines at the top-level call (depth 0) so each shader
|
|
* file gets a fresh set of preprocessor definitions. */
|
|
if (depth == 0)
|
|
preproc_defines_init(&s_defines);
|
|
|
|
if (!out || depth > 16)
|
|
return out;
|
|
|
|
/* Initialize top-level as active */
|
|
cond_active[0] = true;
|
|
cond_done[0] = true;
|
|
|
|
while (*p)
|
|
{
|
|
const char *line_end = strchr(p, '\n');
|
|
size_t line_len;
|
|
const char *s;
|
|
|
|
if (!line_end)
|
|
line_end = p + strlen(p);
|
|
else
|
|
line_end++;
|
|
|
|
line_len = (size_t)(line_end - p);
|
|
|
|
/* Skip leading whitespace to check for preprocessor directives */
|
|
s = p;
|
|
while (s < line_end && (*s == ' ' || *s == '\t'))
|
|
s++;
|
|
|
|
if (*s == '#')
|
|
{
|
|
const char *dir = s + 1;
|
|
while (dir < line_end && (*dir == ' ' || *dir == '\t'))
|
|
dir++;
|
|
|
|
/* #ifdef NAME */
|
|
if (strncmp(dir, "ifdef", 5) == 0 && (dir[5] == ' ' || dir[5] == '\t'))
|
|
{
|
|
const char *nm = dir + 5;
|
|
const char *nm_end;
|
|
while (*nm == ' ' || *nm == '\t') nm++;
|
|
nm_end = nm;
|
|
while (nm_end < line_end && *nm_end != ' ' && *nm_end != '\t'
|
|
&& *nm_end != '\r' && *nm_end != '\n')
|
|
nm_end++;
|
|
|
|
if (cond_depth < PREPROC_MAX_DEPTH - 1)
|
|
{
|
|
cond_depth++;
|
|
if (output_enabled)
|
|
{
|
|
bool def = preproc_is_defined(&s_defines, nm, (size_t)(nm_end - nm));
|
|
cond_active[cond_depth] = def;
|
|
cond_done[cond_depth] = def;
|
|
output_enabled = def;
|
|
}
|
|
else
|
|
{
|
|
cond_active[cond_depth] = false;
|
|
cond_done[cond_depth] = true; /* parent inactive */
|
|
}
|
|
}
|
|
p = line_end;
|
|
continue;
|
|
}
|
|
|
|
/* #ifndef NAME */
|
|
if (strncmp(dir, "ifndef", 6) == 0 && (dir[6] == ' ' || dir[6] == '\t'))
|
|
{
|
|
const char *nm = dir + 6;
|
|
const char *nm_end;
|
|
while (*nm == ' ' || *nm == '\t') nm++;
|
|
nm_end = nm;
|
|
while (nm_end < line_end && *nm_end != ' ' && *nm_end != '\t'
|
|
&& *nm_end != '\r' && *nm_end != '\n')
|
|
nm_end++;
|
|
|
|
if (cond_depth < PREPROC_MAX_DEPTH - 1)
|
|
{
|
|
cond_depth++;
|
|
if (output_enabled)
|
|
{
|
|
bool def = preproc_is_defined(&s_defines, nm, (size_t)(nm_end - nm));
|
|
cond_active[cond_depth] = !def;
|
|
cond_done[cond_depth] = !def;
|
|
output_enabled = !def;
|
|
}
|
|
else
|
|
{
|
|
cond_active[cond_depth] = false;
|
|
cond_done[cond_depth] = true;
|
|
}
|
|
}
|
|
p = line_end;
|
|
continue;
|
|
}
|
|
|
|
/* #if defined(X) / #if !defined(X) / #if defined(X) || defined(Y) */
|
|
if (strncmp(dir, "if", 2) == 0 && (dir[2] == ' ' || dir[2] == '\t'))
|
|
{
|
|
const char *expr = dir + 2;
|
|
bool result = false;
|
|
|
|
while (*expr == ' ' || *expr == '\t') expr++;
|
|
|
|
/* Evaluate compound defined() expressions with || and && */
|
|
{
|
|
bool have_result = false;
|
|
bool current_op_is_or = true; /* default: first term */
|
|
|
|
while (expr < line_end)
|
|
{
|
|
bool negate = false;
|
|
bool term_result = false;
|
|
|
|
while (*expr == ' ' || *expr == '\t' || *expr == '(') expr++;
|
|
if (*expr == '!')
|
|
{
|
|
negate = true;
|
|
expr++;
|
|
while (*expr == ' ' || *expr == '\t') expr++;
|
|
}
|
|
|
|
if (strncmp(expr, "defined", 7) == 0)
|
|
{
|
|
const char *nm, *nm_end;
|
|
expr += 7;
|
|
while (*expr == ' ' || *expr == '\t') expr++;
|
|
if (*expr == '(') expr++;
|
|
while (*expr == ' ' || *expr == '\t') expr++;
|
|
nm = expr;
|
|
nm_end = nm;
|
|
while (nm_end < line_end && d3d9_hlsl_is_ident_char(*nm_end))
|
|
nm_end++;
|
|
term_result = preproc_is_defined(&s_defines, nm, (size_t)(nm_end - nm));
|
|
if (negate) term_result = !term_result;
|
|
expr = nm_end;
|
|
while (*expr == ')' || *expr == ' ' || *expr == '\t') expr++;
|
|
have_result = true;
|
|
}
|
|
else
|
|
break; /* Can't parse further */
|
|
|
|
if (current_op_is_or)
|
|
result = have_result ? (result || term_result) : term_result;
|
|
else
|
|
result = result && term_result;
|
|
|
|
/* Check for || or && */
|
|
if (expr + 1 < line_end && expr[0] == '|' && expr[1] == '|')
|
|
{ current_op_is_or = true; expr += 2; continue; }
|
|
if (expr + 1 < line_end && expr[0] == '&' && expr[1] == '&')
|
|
{ current_op_is_or = false; expr += 2; continue; }
|
|
break;
|
|
}
|
|
|
|
if (have_result)
|
|
{
|
|
if (cond_depth < PREPROC_MAX_DEPTH - 1)
|
|
{
|
|
cond_depth++;
|
|
if (output_enabled)
|
|
{
|
|
cond_active[cond_depth] = result;
|
|
cond_done[cond_depth] = result;
|
|
output_enabled = result;
|
|
}
|
|
else
|
|
{
|
|
cond_active[cond_depth] = false;
|
|
cond_done[cond_depth] = true;
|
|
}
|
|
}
|
|
p = line_end;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* Unhandled #if expression — treat as unknown (skip block) */
|
|
if (cond_depth < PREPROC_MAX_DEPTH - 1)
|
|
{
|
|
cond_depth++;
|
|
cond_active[cond_depth] = false;
|
|
cond_done[cond_depth] = false;
|
|
if (output_enabled)
|
|
output_enabled = false;
|
|
}
|
|
p = line_end;
|
|
continue;
|
|
}
|
|
|
|
/* #elif defined(X) / #elif defined(X) || defined(Y) */
|
|
if (strncmp(dir, "elif", 4) == 0 && (dir[4] == ' ' || dir[4] == '\t'))
|
|
{
|
|
if (cond_depth > 0)
|
|
{
|
|
bool parent_active = (cond_depth <= 1) || cond_active[cond_depth - 1];
|
|
if (parent_active && !cond_done[cond_depth])
|
|
{
|
|
/* Evaluate the elif condition */
|
|
const char *expr = dir + 4;
|
|
bool result = false;
|
|
|
|
while (*expr == ' ' || *expr == '\t') expr++;
|
|
|
|
/* Evaluate compound defined() expressions */
|
|
{
|
|
bool have_result = false;
|
|
bool current_op_is_or = true;
|
|
|
|
while (expr < line_end)
|
|
{
|
|
bool negate = false;
|
|
bool term_result = false;
|
|
|
|
while (*expr == ' ' || *expr == '\t' || *expr == '(') expr++;
|
|
if (*expr == '!')
|
|
{
|
|
negate = true;
|
|
expr++;
|
|
while (*expr == ' ' || *expr == '\t') expr++;
|
|
}
|
|
|
|
if (strncmp(expr, "defined", 7) == 0)
|
|
{
|
|
const char *nm, *nm_end;
|
|
expr += 7;
|
|
while (*expr == ' ' || *expr == '\t') expr++;
|
|
if (*expr == '(') expr++;
|
|
while (*expr == ' ' || *expr == '\t') expr++;
|
|
nm = expr;
|
|
nm_end = nm;
|
|
while (nm_end < line_end && d3d9_hlsl_is_ident_char(*nm_end))
|
|
nm_end++;
|
|
term_result = preproc_is_defined(&s_defines, nm, (size_t)(nm_end - nm));
|
|
if (negate) term_result = !term_result;
|
|
expr = nm_end;
|
|
while (*expr == ')' || *expr == ' ' || *expr == '\t') expr++;
|
|
have_result = true;
|
|
}
|
|
else
|
|
break;
|
|
|
|
if (current_op_is_or)
|
|
result = have_result ? (result || term_result) : term_result;
|
|
else
|
|
result = result && term_result;
|
|
|
|
if (expr + 1 < line_end && expr[0] == '|' && expr[1] == '|')
|
|
{ current_op_is_or = true; expr += 2; continue; }
|
|
if (expr + 1 < line_end && expr[0] == '&' && expr[1] == '&')
|
|
{ current_op_is_or = false; expr += 2; continue; }
|
|
break;
|
|
}
|
|
|
|
if (have_result && result)
|
|
{
|
|
cond_active[cond_depth] = true;
|
|
cond_done[cond_depth] = true;
|
|
output_enabled = true;
|
|
}
|
|
else
|
|
{
|
|
cond_active[cond_depth] = false;
|
|
output_enabled = false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cond_active[cond_depth] = false;
|
|
output_enabled = false;
|
|
}
|
|
}
|
|
p = line_end;
|
|
continue;
|
|
}
|
|
|
|
/* #else */
|
|
if (strncmp(dir, "else", 4) == 0
|
|
&& (dir[4] == '\r' || dir[4] == '\n' || dir[4] == ' '
|
|
|| dir[4] == '\t' || dir[4] == '\0'))
|
|
{
|
|
if (cond_depth > 0)
|
|
{
|
|
/* Check if parent is active */
|
|
bool parent_active = (cond_depth <= 1) || cond_active[cond_depth - 1];
|
|
if (parent_active && !cond_done[cond_depth])
|
|
{
|
|
cond_active[cond_depth] = true;
|
|
cond_done[cond_depth] = true;
|
|
output_enabled = true;
|
|
}
|
|
else
|
|
{
|
|
cond_active[cond_depth] = false;
|
|
output_enabled = false;
|
|
}
|
|
}
|
|
p = line_end;
|
|
continue;
|
|
}
|
|
|
|
/* #endif */
|
|
if (strncmp(dir, "endif", 5) == 0
|
|
&& (dir[5] == '\r' || dir[5] == '\n' || dir[5] == ' '
|
|
|| dir[5] == '\t' || dir[5] == '\0'))
|
|
{
|
|
if (cond_depth > 0)
|
|
cond_depth--;
|
|
/* Recalculate output_enabled */
|
|
output_enabled = true;
|
|
{
|
|
int i;
|
|
for (i = 1; i <= cond_depth; i++)
|
|
if (!cond_active[i])
|
|
{ output_enabled = false; break; }
|
|
}
|
|
p = line_end;
|
|
continue;
|
|
}
|
|
|
|
/* #define NAME [value] — parse and store for expansion */
|
|
if (output_enabled && strncmp(dir, "define", 6) == 0
|
|
&& (dir[6] == ' ' || dir[6] == '\t'))
|
|
{
|
|
const char *nm = dir + 6;
|
|
const char *nm_end;
|
|
while (*nm == ' ' || *nm == '\t') nm++;
|
|
nm_end = nm;
|
|
while (nm_end < line_end && d3d9_hlsl_is_ident_char(*nm_end))
|
|
nm_end++;
|
|
|
|
/* Mark as defined for #ifdef checks */
|
|
if (*nm_end != '(')
|
|
{
|
|
size_t nlen = (size_t)(nm_end - nm);
|
|
char *name_copy = (char*)malloc(nlen + 1);
|
|
if (name_copy)
|
|
{
|
|
memcpy(name_copy, nm, nlen);
|
|
name_copy[nlen] = '\0';
|
|
preproc_add_define(&s_defines, name_copy, NULL);
|
|
}
|
|
}
|
|
/* Line is output below by the regular output path */
|
|
}
|
|
|
|
/* #include handling (only when output is enabled) */
|
|
if (output_enabled && strncmp(dir, "include", 7) == 0)
|
|
{
|
|
const char *q = dir + 7;
|
|
while (q < line_end && (*q == ' ' || *q == '\t'))
|
|
q++;
|
|
|
|
if (*q == '"' || *q == '<')
|
|
{
|
|
char close_char = (*q == '"') ? '"' : '>';
|
|
const char *name_start = q + 1;
|
|
const char *name_end = strchr(name_start, close_char);
|
|
|
|
if (name_end && name_end < line_end)
|
|
{
|
|
char inc_path[PATH_MAX_LENGTH];
|
|
char inc_name[PATH_MAX_LENGTH];
|
|
size_t name_len_n = (size_t)(name_end - name_start);
|
|
char *inc_src;
|
|
|
|
if (name_len_n >= sizeof(inc_name))
|
|
name_len_n = sizeof(inc_name) - 1;
|
|
memcpy(inc_name, name_start, name_len_n);
|
|
inc_name[name_len_n] = '\0';
|
|
|
|
fill_pathname_join(inc_path, base_dir, inc_name,
|
|
sizeof(inc_path));
|
|
|
|
inc_src = d3d9_hlsl_read_file(inc_path, NULL);
|
|
if (!inc_src)
|
|
RARCH_WARN("[D3D9 HLSL] Could not open include: %s\n", inc_path);
|
|
if (inc_src)
|
|
{
|
|
char inc_dir[PATH_MAX_LENGTH];
|
|
char *resolved_inc;
|
|
strlcpy(inc_dir, inc_path, sizeof(inc_dir));
|
|
path_basedir(inc_dir);
|
|
|
|
resolved_inc = d3d9_hlsl_preprocess_includes(
|
|
inc_src, inc_dir, depth + 1);
|
|
free(inc_src);
|
|
|
|
if (resolved_inc)
|
|
{
|
|
size_t resolved_len = strlen(resolved_inc);
|
|
while (pos + resolved_len + 2 >= cap)
|
|
{
|
|
char *tmp;
|
|
cap *= 2;
|
|
if (!(tmp = (char*)realloc(out, cap)))
|
|
{ free(out); free(resolved_inc); return NULL; }
|
|
out = tmp;
|
|
}
|
|
memcpy(out + pos, resolved_inc, resolved_len);
|
|
pos += resolved_len;
|
|
if (resolved_len > 0 && resolved_inc[resolved_len - 1] != '\n')
|
|
out[pos++] = '\n';
|
|
free(resolved_inc);
|
|
}
|
|
|
|
p = line_end;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Output the line if enabled (and not a consumed preprocessor directive) */
|
|
if (output_enabled)
|
|
{
|
|
while (pos + line_len + 1 >= cap)
|
|
{
|
|
char *tmp;
|
|
cap *= 2;
|
|
if (!(tmp = (char*)realloc(out, cap)))
|
|
{ free(out); return NULL; }
|
|
out = tmp;
|
|
}
|
|
memcpy(out + pos, p, line_len);
|
|
pos += line_len;
|
|
}
|
|
|
|
p = line_end;
|
|
}
|
|
|
|
out[pos] = '\0';
|
|
return out;
|
|
}
|
|
|
|
/* =====================================================================
|
|
* Cg → HLSL source-level fixups
|
|
*
|
|
* 1. Whole-word rename 'texture' → '_texture' (HLSL reserved keyword)
|
|
* 2. Extract 'uniform <type> <name>' parameters from function signatures
|
|
* and hoist them to global scope. HLSL requires semantics on every
|
|
* VS/PS input parameter; Cg's 'uniform' qualifier marks parameters
|
|
* that are set by the runtime (not vertex-stream data) which have
|
|
* no semantic. Moving them to global scope makes them plain globals
|
|
* that the HLSL compiler treats as constants (matching CTAB layout).
|
|
* ===================================================================== */
|
|
|
|
static bool d3d9_hlsl_is_ident_char(char c)
|
|
{
|
|
return (c >= 'A' && c <= 'Z')
|
|
|| (c >= 'a' && c <= 'z')
|
|
|| (c >= '0' && c <= '9')
|
|
|| c == '_';
|
|
}
|
|
|
|
/* Append a string to a dynamic buffer, growing as needed. */
|
|
static bool d3d9_hlsl_buf_append(char **buf, size_t *pos, size_t *cap,
|
|
const char *str, size_t len)
|
|
{
|
|
while (*pos + len + 1 >= *cap)
|
|
{
|
|
char *tmp;
|
|
*cap *= 2;
|
|
/* realloc-into-tmp: the pre-patch '*buf = realloc(*buf, *cap)'
|
|
* form leaked the old buffer on OOM (self-assign overwrites
|
|
* the only pointer to it with NULL). On failure free the old
|
|
* buffer and clear the caller's pointer so they don't double-
|
|
* free or use a now-invalid pointer. */
|
|
if (!(tmp = (char*)realloc(*buf, *cap)))
|
|
{
|
|
free(*buf);
|
|
*buf = NULL;
|
|
return false;
|
|
}
|
|
*buf = tmp;
|
|
}
|
|
memcpy(*buf + *pos, str, len);
|
|
*pos += len;
|
|
return true;
|
|
}
|
|
|
|
/* Add TEXCOORD semantics to struct members that lack them.
|
|
* Simple approach: find struct { ... } blocks, check if they qualify,
|
|
* and rewrite the block with semantics added.
|
|
*
|
|
* Returns a malloc'd modified copy, or NULL if no changes needed. */
|
|
static char *d3d9_hlsl_add_struct_semantics(const char *source)
|
|
{
|
|
/* Known structs to skip — these are uniform/input structs that
|
|
* must NOT get TEXCOORD semantics added to their members.
|
|
* 'input' is the Cg compat struct for video_size/texture_size/etc.
|
|
* 'in_vertex' is a dummy struct for the vertex input macro. */
|
|
static const char *skip_names[] = {
|
|
"input",
|
|
"in_vertex",
|
|
NULL
|
|
};
|
|
const char *p = source;
|
|
int texcoord_counter = 2;
|
|
|
|
/* First pass: find if any struct qualifies. If not, return NULL early. */
|
|
bool any_qualify = false;
|
|
{
|
|
const char *s = source;
|
|
while ((s = strstr(s, "struct")) != NULL)
|
|
{
|
|
if ((s == source || !d3d9_hlsl_is_ident_char(s[-1]))
|
|
&& !d3d9_hlsl_is_ident_char(s[6]))
|
|
{
|
|
const char *nm = s + 6;
|
|
const char *nm_end;
|
|
size_t nm_len;
|
|
bool skip = false;
|
|
int i;
|
|
|
|
while (*nm == ' ' || *nm == '\t' || *nm == '\n' || *nm == '\r') nm++;
|
|
nm_end = nm;
|
|
while (d3d9_hlsl_is_ident_char(*nm_end)) nm_end++;
|
|
nm_len = (size_t)(nm_end - nm);
|
|
if (nm_len == 0) { s++; continue; }
|
|
|
|
for (i = 0; skip_names[i]; i++)
|
|
if (strlen(skip_names[i]) == nm_len
|
|
&& strncmp(nm, skip_names[i], nm_len) == 0)
|
|
{ skip = true; break; }
|
|
|
|
if (!skip)
|
|
{
|
|
/* Find { ... } */
|
|
const char *ob = nm_end;
|
|
while (*ob && *ob != '{' && *ob != ';') ob++;
|
|
if (*ob == '{')
|
|
{
|
|
const char *cb = ob + 1;
|
|
int d = 1;
|
|
bool any_no_sem = false, has_sampler = false, any_mem = false;
|
|
while (*cb && d > 0)
|
|
{
|
|
if (*cb == '{') d++;
|
|
else if (*cb == '}') d--;
|
|
else if (d == 1 && strncmp(cb, "sampler", 7) == 0) has_sampler = true;
|
|
else if (*cb == ';' && d == 1)
|
|
{
|
|
any_mem = true;
|
|
/* Check if this specific member has ':' */
|
|
{
|
|
const char *ms = cb - 1;
|
|
bool mem_has_sem = false;
|
|
while (ms > ob && *ms != ';' && *ms != '{')
|
|
{
|
|
if (*ms == ':') mem_has_sem = true;
|
|
ms--;
|
|
}
|
|
if (!mem_has_sem)
|
|
any_no_sem = true;
|
|
}
|
|
}
|
|
cb++;
|
|
}
|
|
if (any_mem && any_no_sem && !has_sampler)
|
|
any_qualify = true;
|
|
}
|
|
}
|
|
}
|
|
s++;
|
|
}
|
|
}
|
|
|
|
if (!any_qualify)
|
|
return NULL;
|
|
|
|
|
|
/* Second pass: copy source, rewriting qualifying structs */
|
|
{
|
|
size_t src_len = strlen(source);
|
|
size_t cap = src_len + 1024;
|
|
size_t opos = 0;
|
|
char *out = (char*)malloc(cap);
|
|
if (!out) return NULL;
|
|
|
|
p = source;
|
|
while (*p)
|
|
{
|
|
if (strncmp(p, "struct", 6) == 0
|
|
&& (p == source || !d3d9_hlsl_is_ident_char(p[-1]))
|
|
&& !d3d9_hlsl_is_ident_char(p[6]))
|
|
{
|
|
const char *nm = p + 6;
|
|
const char *nm_end;
|
|
size_t nm_len;
|
|
bool skip = false;
|
|
int i;
|
|
|
|
while (*nm == ' ' || *nm == '\t' || *nm == '\n' || *nm == '\r') nm++;
|
|
nm_end = nm;
|
|
while (d3d9_hlsl_is_ident_char(*nm_end)) nm_end++;
|
|
nm_len = (size_t)(nm_end - nm);
|
|
|
|
if (nm_len == 0) { p++; continue; }
|
|
for (i = 0; skip_names[i]; i++)
|
|
if (strlen(skip_names[i]) == nm_len
|
|
&& strncmp(nm, skip_names[i], nm_len) == 0)
|
|
{ skip = true; break; }
|
|
|
|
if (!skip)
|
|
{
|
|
const char *ob = nm_end;
|
|
while (*ob && *ob != '{' && *ob != ';') ob++;
|
|
if (*ob == '{')
|
|
{
|
|
const char *cb = ob + 1;
|
|
int d = 1;
|
|
bool any_no_sem = false, has_sampler = false, any_mem = false;
|
|
while (*cb && d > 0)
|
|
{
|
|
if (*cb == '{') d++;
|
|
else if (*cb == '}') d--;
|
|
else if (d == 1 && strncmp(cb, "sampler", 7) == 0) has_sampler = true;
|
|
else if (*cb == ';' && d == 1)
|
|
{
|
|
any_mem = true;
|
|
{
|
|
const char *ms = cb - 1;
|
|
bool mem_has_sem = false;
|
|
while (ms > ob && *ms != ';' && *ms != '{')
|
|
{
|
|
if (*ms == ':') mem_has_sem = true;
|
|
ms--;
|
|
}
|
|
if (!mem_has_sem)
|
|
any_no_sem = true;
|
|
}
|
|
}
|
|
cb++;
|
|
}
|
|
|
|
if (any_mem && any_no_sem && !has_sampler)
|
|
{
|
|
/* Copy from p to ob (inclusive of '{') */
|
|
{
|
|
size_t len = (size_t)(ob + 1 - p);
|
|
while (opos + len + 1 >= cap)
|
|
{
|
|
char *tmp;
|
|
cap *= 2;
|
|
if (!(tmp = (char*)realloc(out, cap)))
|
|
{ free(out); return NULL; }
|
|
out = tmp;
|
|
}
|
|
memcpy(out + opos, p, len);
|
|
opos += len;
|
|
}
|
|
|
|
/* Copy struct body, inserting TEXCOORD only before
|
|
* ';' on members that don't already have ':' */
|
|
/* First, scan for existing TEXCOORD indices to avoid conflicts */
|
|
{
|
|
const char *scan_tc = ob + 1;
|
|
while (scan_tc < cb - 1)
|
|
{
|
|
if (strncmp(scan_tc, "TEXCOORD", 8) == 0)
|
|
{
|
|
int idx = 0;
|
|
const char *dp = scan_tc + 8;
|
|
while (*dp >= '0' && *dp <= '9')
|
|
{
|
|
idx = idx * 10 + (*dp - '0');
|
|
dp++;
|
|
}
|
|
if (idx >= texcoord_counter)
|
|
texcoord_counter = idx + 1;
|
|
}
|
|
scan_tc++;
|
|
}
|
|
}
|
|
{
|
|
const char *mp = ob + 1;
|
|
while (mp < cb - 1)
|
|
{
|
|
if (*mp == ';')
|
|
{
|
|
/* Check if this member already has ':' */
|
|
bool has_colon = false;
|
|
{
|
|
size_t bi = opos;
|
|
while (bi > 0 && out[bi-1] != ';' && out[bi-1] != '{')
|
|
{
|
|
if (out[bi-1] == ':') has_colon = true;
|
|
bi--;
|
|
}
|
|
}
|
|
if (!has_colon)
|
|
{
|
|
char sem[32];
|
|
size_t sl = snprintf(sem, sizeof(sem),
|
|
" : TEXCOORD%d", texcoord_counter++);
|
|
while (opos + sl + 2 >= cap)
|
|
{
|
|
char *tmp;
|
|
cap *= 2;
|
|
if (!(tmp = (char*)realloc(out, cap)))
|
|
{ free(out); return NULL; }
|
|
out = tmp;
|
|
}
|
|
memcpy(out + opos, sem, sl);
|
|
opos += sl;
|
|
}
|
|
}
|
|
while (opos + 2 >= cap)
|
|
{
|
|
char *tmp;
|
|
cap *= 2;
|
|
if (!(tmp = (char*)realloc(out, cap)))
|
|
{ free(out); return NULL; }
|
|
out = tmp;
|
|
}
|
|
out[opos++] = *mp++;
|
|
}
|
|
}
|
|
|
|
/* Copy '}' and advance past struct */
|
|
while (opos + 2 >= cap)
|
|
{
|
|
char *tmp;
|
|
cap *= 2;
|
|
if (!(tmp = (char*)realloc(out, cap)))
|
|
{ free(out); return NULL; }
|
|
out = tmp;
|
|
}
|
|
out[opos++] = '}';
|
|
p = cb;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Regular char */
|
|
while (opos + 2 >= cap)
|
|
{
|
|
char *tmp;
|
|
cap *= 2;
|
|
if (!(tmp = (char*)realloc(out, cap)))
|
|
{ free(out); return NULL; }
|
|
out = tmp;
|
|
}
|
|
out[opos++] = *p++;
|
|
}
|
|
|
|
out[opos] = '\0';
|
|
return out;
|
|
}
|
|
}
|
|
|
|
/* Decompose sampler members out of structs for HLSL SM3.0 compatibility.
|
|
*
|
|
* HLSL SM3.0 doesn't allow samplers to be members of compound types.
|
|
* This pass:
|
|
* 1. Removes 'sampler2D _texture;' lines from struct bodies
|
|
* 2. For each 'uniform <structtype> VARNAME;', emits a standalone
|
|
* 'sampler2D VARNAME__texture;' declaration
|
|
* 3. Replaces 'VARNAME._texture' with 'VARNAME__texture' everywhere
|
|
*
|
|
* Returns a malloc'd modified copy, or NULL if no changes needed. */
|
|
static char *d3d9_hlsl_decompose_struct_samplers(const char *source)
|
|
{
|
|
/* Collect struct names that contain sampler members */
|
|
const char *sampler_structs[16];
|
|
size_t sampler_struct_lens[16];
|
|
unsigned sampler_struct_count = 0;
|
|
|
|
/* Collect instance variable names of those structs */
|
|
const char *instance_names[32];
|
|
size_t instance_name_lens[32];
|
|
unsigned instance_count = 0;
|
|
|
|
size_t src_len, cap, opos;
|
|
char *out;
|
|
const char *p;
|
|
bool modified = false;
|
|
|
|
/* Pass 1: find structs with sampler members */
|
|
{
|
|
const char *s = source;
|
|
while ((s = strstr(s, "struct")) != NULL)
|
|
{
|
|
if ((s == source || !d3d9_hlsl_is_ident_char(s[-1]))
|
|
&& !d3d9_hlsl_is_ident_char(s[6]))
|
|
{
|
|
const char *nm = s + 6;
|
|
const char *nm_end;
|
|
while (*nm == ' ' || *nm == '\t' || *nm == '\n' || *nm == '\r') nm++;
|
|
nm_end = nm;
|
|
while (d3d9_hlsl_is_ident_char(*nm_end)) nm_end++;
|
|
|
|
if (nm_end > nm)
|
|
{
|
|
/* Find struct body */
|
|
const char *ob = nm_end;
|
|
while (*ob && *ob != '{' && *ob != ';') ob++;
|
|
if (*ob == '{')
|
|
{
|
|
const char *cb = ob + 1;
|
|
int d = 1;
|
|
bool has_sampler = false;
|
|
while (*cb && d > 0)
|
|
{
|
|
if (*cb == '{') d++;
|
|
else if (*cb == '}') d--;
|
|
else if (d == 1 && strncmp(cb, "sampler", 7) == 0)
|
|
has_sampler = true;
|
|
cb++;
|
|
}
|
|
if (has_sampler && sampler_struct_count < 16)
|
|
{
|
|
sampler_structs[sampler_struct_count] = nm;
|
|
sampler_struct_lens[sampler_struct_count] = (size_t)(nm_end - nm);
|
|
sampler_struct_count++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
s++;
|
|
}
|
|
}
|
|
|
|
if (sampler_struct_count == 0)
|
|
return NULL;
|
|
|
|
/* Pass 2: find instances of sampler-containing structs
|
|
* Pattern: 'uniform <structname> VARNAME;' or just '<structname> VARNAME;' at global scope */
|
|
{
|
|
const char *s = source;
|
|
unsigned si;
|
|
while (*s)
|
|
{
|
|
for (si = 0; si < sampler_struct_count; si++)
|
|
{
|
|
size_t slen = sampler_struct_lens[si];
|
|
if (strncmp(s, sampler_structs[si], slen) == 0
|
|
&& !d3d9_hlsl_is_ident_char(s[slen])
|
|
&& (s == source || !d3d9_hlsl_is_ident_char(s[-1])
|
|
|| (s >= source + 8
|
|
&& strncmp(s - 8, "uniform ", 8) == 0)))
|
|
{
|
|
/* Find instance name after struct type */
|
|
const char *vn = s + slen;
|
|
const char *vn_end;
|
|
while (*vn == ' ' || *vn == '\t') vn++;
|
|
vn_end = vn;
|
|
while (d3d9_hlsl_is_ident_char(*vn_end)) vn_end++;
|
|
|
|
if (vn_end > vn && instance_count < 32)
|
|
{
|
|
instance_names[instance_count] = vn;
|
|
instance_name_lens[instance_count] = (size_t)(vn_end - vn);
|
|
instance_count++;
|
|
}
|
|
}
|
|
}
|
|
s++;
|
|
}
|
|
}
|
|
|
|
/* Pass 3: rewrite source */
|
|
src_len = strlen(source);
|
|
cap = src_len + instance_count * 64 + 1024;
|
|
opos = 0;
|
|
out = (char*)malloc(cap);
|
|
if (!out) return NULL;
|
|
|
|
p = source;
|
|
while (*p)
|
|
{
|
|
/* Remove sampler member lines from sampler-containing structs.
|
|
* Look for lines containing 'sampler' followed by ';' */
|
|
{
|
|
/* Simpler approach: detect 'sampler2D _texture;' or 'sampler2D _texture ;'
|
|
* as a standalone line (with optional leading whitespace) */
|
|
{
|
|
/* Check if we're at line start */
|
|
if (p == source || p[-1] == '\n')
|
|
{
|
|
const char *ls = p;
|
|
while (*ls == ' ' || *ls == '\t') ls++;
|
|
/* Skip optional 'uniform' keyword */
|
|
if (strncmp(ls, "uniform", 7) == 0
|
|
&& !d3d9_hlsl_is_ident_char(ls[7]))
|
|
{
|
|
ls += 7;
|
|
while (*ls == ' ' || *ls == '\t') ls++;
|
|
}
|
|
if (strncmp(ls, "sampler2D", 9) == 0
|
|
&& !d3d9_hlsl_is_ident_char(ls[9]))
|
|
{
|
|
const char *after_type = ls + 9;
|
|
while (*after_type == ' ' || *after_type == '\t') after_type++;
|
|
if (strncmp(after_type, "_texture", 8) == 0
|
|
&& !d3d9_hlsl_is_ident_char(after_type[8]))
|
|
{
|
|
/* Check this is inside a struct (find ';' on this line) */
|
|
const char *semi = after_type + 8;
|
|
while (*semi == ' ' || *semi == '\t') semi++;
|
|
/* Skip optional ': SEMANTIC' annotation */
|
|
if (*semi == ':')
|
|
{
|
|
semi++;
|
|
while (*semi && *semi != ';' && *semi != '\n') semi++;
|
|
}
|
|
if (*semi == ';')
|
|
{
|
|
/* Check if we're inside a struct body by looking
|
|
* for a preceding '{' without a matching '}' */
|
|
/* Simple heuristic: skip this line */
|
|
const char *nl = strchr(semi, '\n');
|
|
if (nl) p = nl + 1; else p = semi + 1 + strlen(semi + 1);
|
|
modified = true;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* After 'uniform <structname> VARNAME;', add 'sampler2D VARNAME__texture;' */
|
|
{
|
|
unsigned ii;
|
|
for (ii = 0; ii < instance_count; ii++)
|
|
{
|
|
size_t ilen = instance_name_lens[ii];
|
|
if (strncmp(p, instance_names[ii], ilen) == 0
|
|
&& !d3d9_hlsl_is_ident_char(p[ilen])
|
|
&& (p == source || !d3d9_hlsl_is_ident_char(p[-1])))
|
|
{
|
|
/* Check if followed by ';' (this is the declaration) */
|
|
const char *after = p + ilen;
|
|
while (*after == ' ' || *after == '\t') after++;
|
|
if (*after == ';')
|
|
{
|
|
/* Copy 'VARNAME;' then add sampler declaration */
|
|
size_t chunk = (size_t)(after + 1 - p);
|
|
while (opos + chunk + ilen + 40 >= cap)
|
|
{
|
|
char *tmp;
|
|
cap *= 2;
|
|
if (!(tmp = (char*)realloc(out, cap)))
|
|
{ free(out); return NULL; }
|
|
out = tmp;
|
|
}
|
|
memcpy(out + opos, p, chunk);
|
|
opos += chunk;
|
|
|
|
/* Add newline + sampler declaration */
|
|
opos += snprintf(out + opos, cap - opos,
|
|
"\nsampler2D %.*s__texture;",
|
|
(int)ilen, instance_names[ii]);
|
|
|
|
p = after + 1;
|
|
modified = true;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Replace ._texture with __texture globally.
|
|
* Any struct member access on _texture should be decomposed
|
|
* since _texture was our renamed sampler member.
|
|
* Special case: in #define macros with ## token pasting,
|
|
* use ##__texture to preserve correct token pasting. */
|
|
if (*p == '.' && strncmp(p + 1, "_texture", 8) == 0
|
|
&& !d3d9_hlsl_is_ident_char(p[9]))
|
|
{
|
|
/* Check if preceded by ## (token paste operator) */
|
|
bool has_paste = (opos >= 2 && out[opos-1] == '#' && out[opos-2] == '#');
|
|
/* Also check for ##<identifier>. before ._texture:
|
|
* e.g. ##c._texture — the ## was before the identifier, not before the dot */
|
|
if (!has_paste && opos >= 1 && d3d9_hlsl_is_ident_char(out[opos-1]))
|
|
{
|
|
/* Scan back past the identifier to see if ## precedes it */
|
|
size_t bi = opos - 1;
|
|
while (bi > 0 && d3d9_hlsl_is_ident_char(out[bi-1])) bi--;
|
|
if (bi >= 2 && out[bi-1] == '#' && out[bi-2] == '#')
|
|
has_paste = true;
|
|
}
|
|
|
|
while (opos + 14 >= cap)
|
|
{
|
|
char *tmp;
|
|
cap *= 2;
|
|
if (!(tmp = (char*)realloc(out, cap)))
|
|
{ free(out); return NULL; }
|
|
out = tmp;
|
|
}
|
|
|
|
if (has_paste)
|
|
{
|
|
memcpy(out + opos, "##__texture", 11);
|
|
opos += 11;
|
|
}
|
|
else
|
|
{
|
|
memcpy(out + opos, "__texture", 9);
|
|
opos += 9;
|
|
}
|
|
p += 9; /* skip ._texture */
|
|
modified = true;
|
|
continue;
|
|
}
|
|
|
|
/* Regular char */
|
|
while (opos + 2 >= cap)
|
|
{
|
|
char *tmp;
|
|
cap *= 2;
|
|
if (!(tmp = (char*)realloc(out, cap)))
|
|
{ free(out); return NULL; }
|
|
out = tmp;
|
|
}
|
|
out[opos++] = *p++;
|
|
}
|
|
|
|
if (!modified)
|
|
{
|
|
free(out);
|
|
return NULL;
|
|
}
|
|
|
|
out[opos] = '\0';
|
|
return out;
|
|
}
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma optimize("", off) /* Avoid MSVC ICE on complex function */
|
|
#endif
|
|
static char *d3d9_hlsl_fixup_cg_source(const char *source)
|
|
{
|
|
size_t src_len = strlen(source);
|
|
size_t cap = src_len * 2 + 4096;
|
|
size_t pos = 0;
|
|
char *out = (char*)malloc(cap);
|
|
const char *p = source;
|
|
int paren_depth = 0;
|
|
int brace_depth = 0; /* track { } to know when we're at global scope */
|
|
size_t last_func_start = 0;
|
|
|
|
/* Track struct names for constructor replacement */
|
|
const char *struct_names[64];
|
|
size_t struct_name_lens[64];
|
|
unsigned struct_count = 0;
|
|
|
|
if (!out)
|
|
return NULL;
|
|
|
|
while (*p)
|
|
{
|
|
/* Skip #define lines entirely — don't apply any fixups to
|
|
* preprocessor macro definitions. Check if we're at a line start. */
|
|
{
|
|
bool at_line_start = (p == source || p[-1] == '\n');
|
|
if (at_line_start)
|
|
{
|
|
const char *d = p;
|
|
while (*d == ' ' || *d == '\t') d++;
|
|
if (*d == '#')
|
|
{
|
|
const char *dir = d + 1;
|
|
while (*dir == ' ' || *dir == '\t') dir++;
|
|
if (strncmp(dir, "define", 6) == 0
|
|
&& (dir[6] == ' ' || dir[6] == '\t'))
|
|
{
|
|
/* Find the macro name */
|
|
const char *mname = dir + 6;
|
|
const char *mname_end;
|
|
const char *eol;
|
|
|
|
while (*mname == ' ' || *mname == '\t') mname++;
|
|
mname_end = mname;
|
|
while (d3d9_hlsl_is_ident_char(*mname_end)) mname_end++;
|
|
|
|
/* Find end of line (with continuation) */
|
|
eol = p;
|
|
while (*eol)
|
|
{
|
|
const char *nl = strchr(eol, '\n');
|
|
if (!nl) { eol += strlen(eol); break; }
|
|
if (nl > eol && nl[-1] == '\\')
|
|
{ eol = nl + 1; continue; }
|
|
eol = nl + 1;
|
|
break;
|
|
}
|
|
|
|
/* Check if this is COMPAT_IN_VERTEX or COMPAT_IN_FRAGMENT */
|
|
{
|
|
bool is_vertex = ((size_t)(mname_end - mname) == 16
|
|
&& strncmp(mname, "COMPAT_IN_VERTEX", 16) == 0);
|
|
bool is_fragment = ((size_t)(mname_end - mname) == 18
|
|
&& strncmp(mname, "COMPAT_IN_FRAGMENT", 18) == 0);
|
|
|
|
if (is_vertex || is_fragment)
|
|
{
|
|
/* Parse the macro value as a comma-separated param list.
|
|
* Split into params WITH semantics (kept in #define) and
|
|
* params WITHOUT (emitted as global declarations before). */
|
|
const char *val_start = mname_end;
|
|
while (*val_start == ' ' || *val_start == '\t') val_start++;
|
|
|
|
/* Collect params */
|
|
{
|
|
const char *vp = val_start;
|
|
const char *val_end = eol;
|
|
char globals_buf[2048];
|
|
char kept_buf[2048];
|
|
size_t gpos = 0, kpos = 0;
|
|
|
|
/* Trim trailing whitespace/newline from value */
|
|
while (val_end > val_start
|
|
&& (val_end[-1] == '\n' || val_end[-1] == '\r'
|
|
|| val_end[-1] == ' ' || val_end[-1] == '\t'))
|
|
val_end--;
|
|
|
|
while (vp < val_end)
|
|
{
|
|
const char *param_start = vp;
|
|
const char *param_end;
|
|
bool has_colon = false;
|
|
|
|
/* Find end of this param */
|
|
while (vp < val_end && *vp != ',')
|
|
{
|
|
if (*vp == ':') has_colon = true;
|
|
vp++;
|
|
}
|
|
param_end = vp;
|
|
if (vp < val_end) vp++; /* skip comma */
|
|
|
|
/* Trim whitespace */
|
|
while (param_start < param_end
|
|
&& (*param_start == ' ' || *param_start == '\t'))
|
|
param_start++;
|
|
while (param_end > param_start
|
|
&& (param_end[-1] == ' ' || param_end[-1] == '\t'))
|
|
param_end--;
|
|
|
|
if (param_end <= param_start)
|
|
continue;
|
|
|
|
if (has_colon)
|
|
{
|
|
/* Already has semantic — keep as-is */
|
|
if (kpos > 0)
|
|
{ kept_buf[kpos++] = ','; kept_buf[kpos++] = ' '; }
|
|
memcpy(kept_buf + kpos, param_start,
|
|
(size_t)(param_end - param_start));
|
|
kpos += (size_t)(param_end - param_start);
|
|
}
|
|
else
|
|
{
|
|
/* No semantic. If 'uniform', extract as global.
|
|
* Otherwise (struct param like 'in_vertex VIN'),
|
|
* keep in #define but DO NOT add semantic —
|
|
* just extract as a global uniform too. */
|
|
const char *ds = param_start;
|
|
bool is_uniform = false;
|
|
if (strncmp(ds, "uniform", 7) == 0
|
|
&& !d3d9_hlsl_is_ident_char(ds[7]))
|
|
{
|
|
is_uniform = true;
|
|
ds += 7;
|
|
while (*ds == ' ' || *ds == '\t') ds++;
|
|
}
|
|
|
|
/* Check has two tokens (type + name) */
|
|
{
|
|
const char *tk = ds;
|
|
while (tk < param_end && d3d9_hlsl_is_ident_char(*tk)) tk++;
|
|
while (tk < param_end && (*tk == ' ' || *tk == '\t')) tk++;
|
|
if (tk < param_end && d3d9_hlsl_is_ident_char(*tk))
|
|
{
|
|
if (is_uniform)
|
|
{
|
|
/* Uniform param — always extract as global */
|
|
gpos += snprintf(globals_buf + gpos,
|
|
sizeof(globals_buf) - gpos,
|
|
"uniform %.*s;\n",
|
|
(int)(param_end - ds), ds);
|
|
}
|
|
else if (is_vertex)
|
|
{
|
|
/* VS non-uniform struct param (in_vertex VIN)
|
|
* — extract as global since the struct has
|
|
* no meaningful semantics (just a dummy). */
|
|
gpos += snprintf(globals_buf + gpos,
|
|
sizeof(globals_buf) - gpos,
|
|
"static %.*s;\n",
|
|
(int)(param_end - ds), ds);
|
|
}
|
|
else
|
|
{
|
|
/* Non-uniform struct param (e.g. in_vertex VIN,
|
|
* out_vertex VOUT) — keep in #define as-is.
|
|
* These are vertex/rasterizer inputs with
|
|
* per-member semantics. */
|
|
if (kpos > 0)
|
|
{ kept_buf[kpos++] = ','; kept_buf[kpos++] = ' '; }
|
|
memcpy(kept_buf + kpos, param_start,
|
|
(size_t)(param_end - param_start));
|
|
kpos += (size_t)(param_end - param_start);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Single token (macro?) — keep in #define */
|
|
if (kpos > 0)
|
|
{ kept_buf[kpos++] = ','; kept_buf[kpos++] = ' '; }
|
|
memcpy(kept_buf + kpos, param_start,
|
|
(size_t)(param_end - param_start));
|
|
kpos += (size_t)(param_end - param_start);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
kept_buf[kpos] = '\0';
|
|
globals_buf[gpos] = '\0';
|
|
|
|
/* Output: globals first (with dedup), then rewritten #define */
|
|
if (gpos > 0)
|
|
{
|
|
/* Check each global line for duplicates before emitting */
|
|
char *gl = globals_buf;
|
|
while (*gl)
|
|
{
|
|
char *gl_end = strchr(gl, '\n');
|
|
size_t gl_len;
|
|
if (!gl_end) gl_end = gl + strlen(gl);
|
|
else gl_end++;
|
|
gl_len = (size_t)(gl_end - gl);
|
|
|
|
/* Check if this declaration exists in output */
|
|
out[pos] = '\0';
|
|
if (!strstr(out, gl))
|
|
d3d9_hlsl_buf_append(&out, &pos, &cap, gl, gl_len);
|
|
|
|
gl = gl_end;
|
|
}
|
|
}
|
|
|
|
{
|
|
char def_line[2048];
|
|
size_t dl = snprintf(def_line, sizeof(def_line),
|
|
"#define %.*s %s\n",
|
|
(int)(mname_end - mname), mname,
|
|
kept_buf);
|
|
d3d9_hlsl_buf_append(&out, &pos, &cap,
|
|
def_line, dl);
|
|
}
|
|
}
|
|
|
|
p = eol;
|
|
continue;
|
|
}
|
|
} /* is_vertex || is_fragment */
|
|
|
|
/* Regular #define — copy verbatim */
|
|
{
|
|
size_t ll = (size_t)(eol - p);
|
|
/* Copy #define line, but apply texture -> _texture rename */
|
|
{
|
|
const char *dp = p;
|
|
const char *de = p + ll;
|
|
while (dp < de)
|
|
{
|
|
if (strncmp(dp, "texture", 7) == 0
|
|
&& (dp == p || !d3d9_hlsl_is_ident_char(dp[-1]))
|
|
&& !d3d9_hlsl_is_ident_char(dp[7]))
|
|
{
|
|
if (!d3d9_hlsl_buf_append(&out, &pos, &cap, "_texture", 8))
|
|
goto fail;
|
|
dp += 7;
|
|
}
|
|
else
|
|
{
|
|
if (!d3d9_hlsl_buf_append(&out, &pos, &cap, dp, 1))
|
|
goto fail;
|
|
dp++;
|
|
}
|
|
}
|
|
}
|
|
p = eol;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* --- Track struct declarations for constructor replacement --- */
|
|
if (brace_depth == 0 && paren_depth == 0
|
|
&& strncmp(p, "struct", 6) == 0
|
|
&& (p == source || !d3d9_hlsl_is_ident_char(p[-1]))
|
|
&& !d3d9_hlsl_is_ident_char(p[6]))
|
|
{
|
|
const char *nm = p + 6;
|
|
while (*nm == ' ' || *nm == '\t' || *nm == '\r' || *nm == '\n')
|
|
nm++;
|
|
if (d3d9_hlsl_is_ident_char(*nm))
|
|
{
|
|
const char *nm_end = nm;
|
|
while (d3d9_hlsl_is_ident_char(*nm_end)) nm_end++;
|
|
if (struct_count < 64)
|
|
{
|
|
struct_names[struct_count] = nm;
|
|
struct_name_lens[struct_count] = (size_t)(nm_end - nm);
|
|
struct_count++;
|
|
}
|
|
}
|
|
/* Don't consume — fall through to normal output */
|
|
}
|
|
|
|
/* --- Fix 1: whole-word 'texture' -> '_texture' --- */
|
|
if (strncmp(p, "texture", 7) == 0
|
|
&& (p == source || !d3d9_hlsl_is_ident_char(p[-1]))
|
|
&& !d3d9_hlsl_is_ident_char(p[7]))
|
|
{
|
|
if (!d3d9_hlsl_buf_append(&out, &pos, &cap, "_texture", 8))
|
|
goto fail;
|
|
p += 7;
|
|
continue;
|
|
}
|
|
|
|
/* --- Fix 1e: whole-word 'mod' -> 'fmod' ---
|
|
* Cg has mod(x,y), HLSL uses fmod(x,y). */
|
|
if (strncmp(p, "mod", 3) == 0
|
|
&& (p == source || !d3d9_hlsl_is_ident_char(p[-1]))
|
|
&& !d3d9_hlsl_is_ident_char(p[3]))
|
|
{
|
|
if (!d3d9_hlsl_buf_append(&out, &pos, &cap, "fmod", 4))
|
|
goto fail;
|
|
p += 3;
|
|
continue;
|
|
}
|
|
|
|
/* --- Fix 1f: standalone 'sampler' -> 'sampler2D' ---
|
|
* Cg uses 'sampler' as a generic type interchangeable with sampler2D.
|
|
* HLSL treats 'sampler' as SamplerState which is incompatible.
|
|
* Only match 'sampler' NOT followed by 1D/2D/3D/CUBE. */
|
|
if (strncmp(p, "sampler", 7) == 0
|
|
&& (p == source || !d3d9_hlsl_is_ident_char(p[-1]))
|
|
&& !d3d9_hlsl_is_ident_char(p[7])
|
|
&& strncmp(p + 7, "2D", 2) != 0
|
|
&& strncmp(p + 7, "1D", 2) != 0
|
|
&& strncmp(p + 7, "3D", 2) != 0
|
|
&& strncmp(p + 7, "CUBE", 4) != 0)
|
|
{
|
|
if (!d3d9_hlsl_buf_append(&out, &pos, &cap, "sampler2D", 9))
|
|
goto fail;
|
|
p += 7;
|
|
continue;
|
|
}
|
|
|
|
/* --- Fix 1g: whole-word 'fract' -> 'frac' ---
|
|
* Cg has fract(x), HLSL uses frac(x). */
|
|
if (strncmp(p, "fract", 5) == 0
|
|
&& (p == source || !d3d9_hlsl_is_ident_char(p[-1]))
|
|
&& !d3d9_hlsl_is_ident_char(p[5]))
|
|
{
|
|
if (!d3d9_hlsl_buf_append(&out, &pos, &cap, "frac", 4))
|
|
goto fail;
|
|
p += 5;
|
|
continue;
|
|
}
|
|
|
|
/* --- Fix 1h: GLSL type renames ---
|
|
* Some Cg shaders use GLSL types. */
|
|
if ((p == source || !d3d9_hlsl_is_ident_char(p[-1])))
|
|
{
|
|
/* vec4 -> float4 */
|
|
if (strncmp(p, "vec4", 4) == 0 && !d3d9_hlsl_is_ident_char(p[4]))
|
|
{ if (!d3d9_hlsl_buf_append(&out, &pos, &cap, "float4", 6)) goto fail; p += 4; continue; }
|
|
/* vec3 -> float3 */
|
|
if (strncmp(p, "vec3", 4) == 0 && !d3d9_hlsl_is_ident_char(p[4]))
|
|
{ if (!d3d9_hlsl_buf_append(&out, &pos, &cap, "float3", 6)) goto fail; p += 4; continue; }
|
|
/* vec2 -> float2 */
|
|
if (strncmp(p, "vec2", 4) == 0 && !d3d9_hlsl_is_ident_char(p[4]))
|
|
{ if (!d3d9_hlsl_buf_append(&out, &pos, &cap, "float2", 6)) goto fail; p += 4; continue; }
|
|
/* mat4 -> float4x4 */
|
|
if (strncmp(p, "mat4", 4) == 0 && !d3d9_hlsl_is_ident_char(p[4]))
|
|
{ if (!d3d9_hlsl_buf_append(&out, &pos, &cap, "float4x4", 8)) goto fail; p += 4; continue; }
|
|
/* mat3 -> float3x3 */
|
|
if (strncmp(p, "mat3", 4) == 0 && !d3d9_hlsl_is_ident_char(p[4]))
|
|
{ if (!d3d9_hlsl_buf_append(&out, &pos, &cap, "float3x3", 8)) goto fail; p += 4; continue; }
|
|
/* mix -> lerp */
|
|
if (strncmp(p, "mix", 3) == 0 && !d3d9_hlsl_is_ident_char(p[3]))
|
|
{ if (!d3d9_hlsl_buf_append(&out, &pos, &cap, "lerp", 4)) goto fail; p += 3; continue; }
|
|
}
|
|
|
|
/* --- Fix 1a: 'const' -> 'static const' at global scope ---
|
|
* D3DCompile with backwards compat treats global 'const' as a CTAB
|
|
* uniform instead of embedding the literal value. 'static const'
|
|
* forces the value to be embedded in the bytecode. */
|
|
if (brace_depth == 0 && paren_depth == 0
|
|
&& strncmp(p, "const", 5) == 0
|
|
&& (p == source || !d3d9_hlsl_is_ident_char(p[-1]))
|
|
&& !d3d9_hlsl_is_ident_char(p[5]))
|
|
{
|
|
/* Check it's not already 'static const' */
|
|
bool already_static = false;
|
|
if (p >= source + 7 && strncmp(p - 7, "static ", 7) == 0)
|
|
already_static = true;
|
|
/* Check for 'const static' — swap to 'static const' */
|
|
{
|
|
const char *after = p + 5;
|
|
while (*after == ' ' || *after == '\t') after++;
|
|
if (strncmp(after, "static", 6) == 0
|
|
&& !d3d9_hlsl_is_ident_char(after[6]))
|
|
{
|
|
/* Emit 'static const' and skip past 'const static' */
|
|
if (!d3d9_hlsl_buf_append(&out, &pos, &cap, "static const", 12))
|
|
goto fail;
|
|
p = after + 6;
|
|
continue;
|
|
}
|
|
}
|
|
if (!already_static)
|
|
{
|
|
if (!d3d9_hlsl_buf_append(&out, &pos, &cap, "static const", 12))
|
|
goto fail;
|
|
p += 5;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* --- Fix 1a2: add 'static' to global float/int declarations with initializers ---
|
|
* D3DCompile treats global 'float x = val;' as a CTAB uniform,
|
|
* ignoring the initializer. 'static float x = val;' embeds it.
|
|
* Only at global scope, not preceded by 'uniform' or 'static'. */
|
|
if (brace_depth == 0 && paren_depth == 0
|
|
&& (strncmp(p, "float", 5) == 0 || strncmp(p, "int", 3) == 0)
|
|
&& (p == source || !d3d9_hlsl_is_ident_char(p[-1])))
|
|
{
|
|
/* Determine the type keyword length */
|
|
size_t tlen = 0;
|
|
if (strncmp(p, "float4x4", 8) == 0 && !d3d9_hlsl_is_ident_char(p[8]))
|
|
tlen = 8;
|
|
else if (strncmp(p, "float4", 6) == 0 && !d3d9_hlsl_is_ident_char(p[6]))
|
|
tlen = 6;
|
|
else if (strncmp(p, "float3x3", 8) == 0 && !d3d9_hlsl_is_ident_char(p[8]))
|
|
tlen = 8;
|
|
else if (strncmp(p, "float3", 6) == 0 && !d3d9_hlsl_is_ident_char(p[6]))
|
|
tlen = 6;
|
|
else if (strncmp(p, "float2", 6) == 0 && !d3d9_hlsl_is_ident_char(p[6]))
|
|
tlen = 6;
|
|
else if (strncmp(p, "float", 5) == 0 && !d3d9_hlsl_is_ident_char(p[5]))
|
|
tlen = 5;
|
|
else if (strncmp(p, "int", 3) == 0 && !d3d9_hlsl_is_ident_char(p[3]))
|
|
tlen = 3;
|
|
|
|
if (tlen > 0)
|
|
{
|
|
/* Check: skip whitespace, identifier, skip whitespace, '=' */
|
|
const char *tk = p + tlen;
|
|
while (*tk == ' ' || *tk == '\t') tk++;
|
|
if (d3d9_hlsl_is_ident_char(*tk))
|
|
{
|
|
while (d3d9_hlsl_is_ident_char(*tk)) tk++;
|
|
while (*tk == ' ' || *tk == '\t') tk++;
|
|
if (*tk == '=' && tk[1] != '=') /* '=' but not '==' */
|
|
{
|
|
/* Check not already preceded by 'static' or 'uniform' */
|
|
bool skip = false;
|
|
if (p >= source + 7 && strncmp(p - 7, "static ", 7) == 0)
|
|
skip = true;
|
|
if (p >= source + 8 && strncmp(p - 8, "uniform ", 8) == 0)
|
|
skip = true;
|
|
/* Also check output buffer for 'static ' or 'uniform ' */
|
|
if (!skip && pos >= 7 && strncmp(out + pos - 7, "static ", 7) == 0)
|
|
skip = true;
|
|
if (!skip && pos >= 8 && strncmp(out + pos - 8, "uniform ", 8) == 0)
|
|
skip = true;
|
|
if (!skip && pos >= 6 && strncmp(out + pos - 6, "const ", 6) == 0)
|
|
skip = true;
|
|
|
|
/* Skip if this variable is a shader parameter declared
|
|
* via '#pragma parameter'. Making it static would bake
|
|
* the default value into the bytecode and prevent the
|
|
* menu from adjusting it at runtime. */
|
|
if (!skip)
|
|
{
|
|
const char *vn = p + tlen;
|
|
const char *vn_end;
|
|
while (*vn == ' ' || *vn == '\t') vn++;
|
|
vn_end = vn;
|
|
while (d3d9_hlsl_is_ident_char(*vn_end)) vn_end++;
|
|
if (vn_end > vn)
|
|
{
|
|
size_t vn_len = (size_t)(vn_end - vn);
|
|
const char *sp = source;
|
|
while ((sp = strstr(sp, "#pragma parameter")) != NULL)
|
|
{
|
|
const char *pp = sp + 17; /* strlen("#pragma parameter") */
|
|
while (*pp == ' ' || *pp == '\t') pp++;
|
|
if (strncmp(pp, vn, vn_len) == 0
|
|
&& !d3d9_hlsl_is_ident_char(pp[vn_len]))
|
|
{
|
|
skip = true;
|
|
break;
|
|
}
|
|
sp = pp;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!skip)
|
|
{
|
|
if (!d3d9_hlsl_buf_append(&out, &pos, &cap, "static ", 7))
|
|
goto fail;
|
|
/* Fall through to copy the type keyword normally */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* --- Fix 1b: strip 'uniform' from struct member declarations ---
|
|
* Cg allows 'uniform sampler2D tex' inside structs; HLSL doesn't. */
|
|
if (brace_depth > 0 && paren_depth == 0
|
|
&& strncmp(p, "uniform", 7) == 0
|
|
&& (p == source || !d3d9_hlsl_is_ident_char(p[-1]))
|
|
&& !d3d9_hlsl_is_ident_char(p[7]))
|
|
{
|
|
/* Skip the 'uniform' keyword and trailing whitespace */
|
|
p += 7;
|
|
while (*p == ' ' || *p == '\t') p++;
|
|
continue;
|
|
}
|
|
|
|
/* --- Fix 1c: replace Cg struct constructors ---
|
|
* Cg: coords = tex_coords(a, b, c);
|
|
* HLSL: { tex_coords _sc = {a, b, c}; coords = _sc; }
|
|
*
|
|
* Detect: inside function body, identifier matching known struct
|
|
* name followed by '(', preceded by '=' in the output. */
|
|
if (brace_depth > 0 && paren_depth == 0
|
|
&& d3d9_hlsl_is_ident_char(*p))
|
|
{
|
|
unsigned si;
|
|
for (si = 0; si < struct_count; si++)
|
|
{
|
|
size_t slen = struct_name_lens[si];
|
|
if (strncmp(p, struct_names[si], slen) == 0
|
|
&& !d3d9_hlsl_is_ident_char(p[slen])
|
|
&& (p == source || !d3d9_hlsl_is_ident_char(p[-1])))
|
|
{
|
|
/* Check if followed by '(' (with optional whitespace) */
|
|
const char *after_name = p + slen;
|
|
while (*after_name == ' ' || *after_name == '\t')
|
|
after_name++;
|
|
|
|
if (*after_name == '(')
|
|
{
|
|
/* Check if preceded by '=' in the output (assignment context) */
|
|
size_t bi = pos;
|
|
while (bi > 0 && (out[bi-1] == ' ' || out[bi-1] == '\t'))
|
|
bi--;
|
|
if (bi > 0 && out[bi-1] == '=')
|
|
{
|
|
/* Find the matching ')' */
|
|
const char *args_start = after_name + 1;
|
|
const char *args_end = args_start;
|
|
int depth = 1;
|
|
while (*args_end && depth > 0)
|
|
{
|
|
if (*args_end == '(') depth++;
|
|
else if (*args_end == ')') depth--;
|
|
if (depth > 0) args_end++;
|
|
}
|
|
|
|
if (depth == 0)
|
|
{
|
|
/* Find the variable name before '=' by scanning
|
|
* backwards in the output past '=' and whitespace */
|
|
size_t var_end = bi - 1; /* before '=' */
|
|
size_t var_start;
|
|
char var_name[256];
|
|
size_t var_len;
|
|
char replacement[4096];
|
|
size_t rlen;
|
|
|
|
while (var_end > 0 && (out[var_end-1] == ' '
|
|
|| out[var_end-1] == '\t'))
|
|
var_end--;
|
|
var_start = var_end;
|
|
while (var_start > 0
|
|
&& (d3d9_hlsl_is_ident_char(out[var_start-1])
|
|
|| out[var_start-1] == '.'))
|
|
var_start--;
|
|
|
|
var_len = var_end - var_start;
|
|
if (var_len > 0 && var_len < sizeof(var_name))
|
|
{
|
|
size_t args_len = (size_t)(args_end - args_start);
|
|
|
|
memcpy(var_name, out + var_start, var_len);
|
|
var_name[var_len] = '\0';
|
|
|
|
/* Build: { structname _sc = {args}; var = _sc; }
|
|
* But we need to replace the "var =" already in the output.
|
|
* Rewind output to var_start and write the replacement. */
|
|
rlen = snprintf(replacement, sizeof(replacement),
|
|
"{ %.*s _sc = {%.*s}; %s = _sc; }",
|
|
(int)slen, struct_names[si],
|
|
(int)args_len, args_start,
|
|
var_name);
|
|
|
|
/* Rewind output to var_start */
|
|
pos = var_start;
|
|
|
|
if (!d3d9_hlsl_buf_append(&out, &pos, &cap,
|
|
replacement, rlen))
|
|
goto fail;
|
|
|
|
/* Skip past ')' in source, and the ';' if present */
|
|
p = args_end + 1; /* skip ')' */
|
|
while (*p == ' ' || *p == '\t') p++;
|
|
if (*p == ';') p++; /* skip ';' */
|
|
|
|
goto struct_ctor_done;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
struct_ctor_done:
|
|
if (si < struct_count)
|
|
continue; /* constructor was replaced */
|
|
}
|
|
|
|
/* --- Fix 1d: expand float3/4(scalar) to float3/4(s,s,...) ---
|
|
* Cg supports float3(x) as a splat constructor. HLSL requires
|
|
* the correct number of arguments.
|
|
* Note: float2(expr) is NOT expanded because it's usually
|
|
* float2(float2_expr) which is a valid HLSL cast. */
|
|
if (strncmp(p, "float", 5) == 0
|
|
&& (p[5] >= '3' && p[5] <= '4')
|
|
&& p[6] == '('
|
|
&& (p == source || !d3d9_hlsl_is_ident_char(p[-1])))
|
|
{
|
|
int n_components = p[5] - '0';
|
|
/* Check if this is a single-argument call (no comma at depth 0) */
|
|
const char *args_start = p + 7;
|
|
const char *scan = args_start;
|
|
int depth = 1;
|
|
bool has_comma = false;
|
|
while (*scan && depth > 0)
|
|
{
|
|
if (*scan == '(') depth++;
|
|
else if (*scan == ')') { if (--depth == 0) break; }
|
|
else if (*scan == ',' && depth == 1) { has_comma = true; break; }
|
|
scan++;
|
|
}
|
|
|
|
if (!has_comma && depth == 0 && scan > args_start)
|
|
{
|
|
/* Single argument — Cg splat constructor float4(x) → all components.
|
|
* Use a cast (float4)(x) which handles both scalar and bool args
|
|
* correctly in HLSL. */
|
|
size_t arg_len = (size_t)(scan - args_start);
|
|
char arg[512];
|
|
if (arg_len < sizeof(arg))
|
|
{
|
|
char cast_buf[32];
|
|
size_t cast_len;
|
|
memcpy(arg, args_start, arg_len);
|
|
arg[arg_len] = '\0';
|
|
|
|
/* Emit ((floatN)(arg)) */
|
|
cast_len = snprintf(cast_buf, sizeof(cast_buf),
|
|
"((float%d)(", n_components);
|
|
if (!d3d9_hlsl_buf_append(&out, &pos, &cap, cast_buf, cast_len))
|
|
goto fail;
|
|
if (!d3d9_hlsl_buf_append(&out, &pos, &cap, arg, arg_len))
|
|
goto fail;
|
|
if (!d3d9_hlsl_buf_append(&out, &pos, &cap, "))", 2))
|
|
goto fail;
|
|
|
|
p = scan + 1; /* skip past ')' */
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* Check for floatN(X, X, X, X) where all args are identical.
|
|
* Cg allows float4(float4_val, float4_val, ...) taking first
|
|
* N components. HLSL does not. Replace with just X. */
|
|
if (has_comma)
|
|
{
|
|
/* Re-scan to collect all arguments */
|
|
const char *a_start[4];
|
|
size_t a_len[4];
|
|
int a_count = 0;
|
|
const char *as = args_start;
|
|
int d2 = 1;
|
|
bool all_same = true;
|
|
|
|
a_start[0] = as;
|
|
while (*as && d2 > 0 && a_count < 4)
|
|
{
|
|
if (*as == '(') d2++;
|
|
else if (*as == ')') { if (--d2 == 0) break; }
|
|
else if (*as == ',' && d2 == 1)
|
|
{
|
|
/* Trim whitespace from current arg */
|
|
const char *ae = as;
|
|
while (ae > a_start[a_count]
|
|
&& (ae[-1] == ' ' || ae[-1] == '\t'))
|
|
ae--;
|
|
while (a_start[a_count] < ae
|
|
&& (*a_start[a_count] == ' '
|
|
|| *a_start[a_count] == '\t'))
|
|
a_start[a_count]++;
|
|
a_len[a_count] = (size_t)(ae - a_start[a_count]);
|
|
a_count++;
|
|
if (a_count < 4)
|
|
a_start[a_count] = as + 1;
|
|
}
|
|
as++;
|
|
}
|
|
/* Last argument */
|
|
if (d2 == 0 && a_count < 4)
|
|
{
|
|
const char *ae = as;
|
|
while (ae > a_start[a_count]
|
|
&& (ae[-1] == ' ' || ae[-1] == '\t'))
|
|
ae--;
|
|
while (a_start[a_count] < ae
|
|
&& (*a_start[a_count] == ' '
|
|
|| *a_start[a_count] == '\t'))
|
|
a_start[a_count]++;
|
|
a_len[a_count] = (size_t)(ae - a_start[a_count]);
|
|
a_count++;
|
|
}
|
|
|
|
if (a_count == n_components && a_count >= 3)
|
|
{
|
|
int ai;
|
|
for (ai = 1; ai < a_count; ai++)
|
|
{
|
|
if (a_len[ai] != a_len[0]
|
|
|| strncmp(a_start[0], a_start[ai], a_len[0]) != 0)
|
|
{ all_same = false; break; }
|
|
}
|
|
|
|
if (all_same && a_len[0] > 0
|
|
&& d3d9_hlsl_is_ident_char(a_start[0][0])
|
|
&& !(a_start[0][0] >= '0' && a_start[0][0] <= '9'))
|
|
{
|
|
if (!d3d9_hlsl_buf_append(&out, &pos, &cap, "(", 1))
|
|
goto fail;
|
|
if (!d3d9_hlsl_buf_append(&out, &pos, &cap,
|
|
a_start[0], a_len[0]))
|
|
goto fail;
|
|
if (!d3d9_hlsl_buf_append(&out, &pos, &cap, ")", 1))
|
|
goto fail;
|
|
p = as + 1; /* skip past ')' */
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Track braces for scope detection */
|
|
if (*p == '{')
|
|
{
|
|
brace_depth++;
|
|
if (!d3d9_hlsl_buf_append(&out, &pos, &cap, p, 1))
|
|
goto fail;
|
|
p++;
|
|
continue;
|
|
}
|
|
if (*p == '}')
|
|
{
|
|
if (brace_depth > 0) brace_depth--;
|
|
if (!d3d9_hlsl_buf_append(&out, &pos, &cap, p, 1))
|
|
goto fail;
|
|
p++;
|
|
continue;
|
|
}
|
|
|
|
/* Track paren depth — only for function definitions, not macro calls.
|
|
* A function definition '(' is preceded by the function name identifier
|
|
* and the line looks like: <type> <name>( ... )
|
|
* We detect this by checking that:
|
|
* - we're at paren_depth 0
|
|
* - the '(' is preceded by an identifier (function name)
|
|
* - the line does NOT start with '#' (not a preprocessor line)
|
|
* - the line does NOT start with 'uniform' (not a uniform declaration) */
|
|
if (*p == '(')
|
|
{
|
|
if (paren_depth == 0 && brace_depth == 0)
|
|
{
|
|
/* Check if this looks like a function definition */
|
|
bool is_func_sig = false;
|
|
|
|
/* Look at the line start to rule out preprocessor/uniform lines */
|
|
{
|
|
size_t ls = pos;
|
|
while (ls > 0 && out[ls - 1] != '\n')
|
|
ls--;
|
|
/* Skip whitespace at line start */
|
|
{
|
|
size_t cs = ls;
|
|
while (cs < pos && (out[cs] == ' ' || out[cs] == '\t'))
|
|
cs++;
|
|
/* Not a preprocessor line and not 'uniform' */
|
|
if (cs < pos && out[cs] != '#'
|
|
&& strncmp(out + cs, "uniform", 7) != 0)
|
|
{
|
|
/* Check that '(' is preceded by an identifier
|
|
* AND the function is main_vertex or main_fragment
|
|
* (don't extract params from helper functions).
|
|
* Scan backward past whitespace/newlines since the
|
|
* name may be on a previous line: main_vertex\n ( */
|
|
{
|
|
size_t ne = pos;
|
|
while (ne > 0 && (out[ne - 1] == ' '
|
|
|| out[ne - 1] == '\t'
|
|
|| out[ne - 1] == '\r'
|
|
|| out[ne - 1] == '\n'))
|
|
ne--;
|
|
if (ne > 0 && d3d9_hlsl_is_ident_char(out[ne - 1]))
|
|
{
|
|
if ((ne >= 11 && strncmp(out + ne - 11, "main_vertex", 11) == 0
|
|
&& (ne == 11 || !d3d9_hlsl_is_ident_char(out[ne - 12])))
|
|
|| (ne >= 13 && strncmp(out + ne - 13, "main_fragment", 13) == 0
|
|
&& (ne == 13 || !d3d9_hlsl_is_ident_char(out[ne - 14]))))
|
|
is_func_sig = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
last_func_start = ls;
|
|
}
|
|
|
|
if (is_func_sig)
|
|
paren_depth++;
|
|
/* else: don't increment paren_depth for macro calls */
|
|
}
|
|
else
|
|
paren_depth++;
|
|
|
|
if (!d3d9_hlsl_buf_append(&out, &pos, &cap, p, 1))
|
|
goto fail;
|
|
p++;
|
|
continue;
|
|
}
|
|
|
|
if (*p == ')')
|
|
{
|
|
if (paren_depth > 0)
|
|
paren_depth--;
|
|
if (!d3d9_hlsl_buf_append(&out, &pos, &cap, p, 1))
|
|
goto fail;
|
|
p++;
|
|
continue;
|
|
}
|
|
|
|
/* --- Fix 2: remove params without semantics from function sigs ---
|
|
*
|
|
* Catches both 'uniform <type> <name>' and plain '<type> <name>'
|
|
* where <name> has no ': SEMANTIC' annotation. These are Cg-style
|
|
* runtime parameters that can't be vertex-stream inputs in HLSL.
|
|
*
|
|
* We detect: inside parens, after '(' or ',', skip whitespace,
|
|
* if we see 'uniform' consume it, then look for '<ident> <ident>'
|
|
* NOT followed by ':'. */
|
|
if (paren_depth > 0 && brace_depth == 0)
|
|
{
|
|
/* Check if we're at the start of a parameter (after '(' or ',') */
|
|
bool at_param_start = false;
|
|
{
|
|
/* Scan backwards in output for the last non-whitespace char */
|
|
size_t si = pos;
|
|
while (si > 0 && (out[si-1] == ' ' || out[si-1] == '\t'
|
|
|| out[si-1] == '\r' || out[si-1] == '\n'))
|
|
si--;
|
|
if (si > 0 && (out[si-1] == '(' || out[si-1] == ','))
|
|
at_param_start = true;
|
|
}
|
|
|
|
if (at_param_start && d3d9_hlsl_is_ident_char(*p))
|
|
{
|
|
/* We're at the start of a parameter. Scan ahead to determine
|
|
* if this is a '<type> <name>' without ': SEMANTIC'.
|
|
*
|
|
* Find the end of this param (next ',' or ')' at depth 0) */
|
|
const char *param_start = p;
|
|
const char *param_end = p;
|
|
bool has_semantic = false;
|
|
{
|
|
int d = 0;
|
|
const char *s = p;
|
|
while (*s)
|
|
{
|
|
if (*s == '(') d++;
|
|
else if (*s == ')')
|
|
{ if (d == 0) break; d--; }
|
|
else if (*s == ',' && d == 0)
|
|
break;
|
|
else if (*s == ':' && d == 0)
|
|
has_semantic = true;
|
|
s++;
|
|
}
|
|
param_end = s;
|
|
}
|
|
|
|
if (!has_semantic)
|
|
{
|
|
/* This param has no semantic — it's a Cg uniform-style param.
|
|
* Extract it, optionally strip leading 'uniform', hoist to global.
|
|
*
|
|
* IMPORTANT: only extract if the declaration has at least
|
|
* two whitespace-separated tokens (type + name). A single
|
|
* identifier is likely a macro (e.g. COMPAT_IN_VERTEX)
|
|
* that D3DCompile will expand. */
|
|
const char *decl_start = param_start;
|
|
const char *decl_end = param_end;
|
|
const char *trim;
|
|
size_t decl_len;
|
|
char decl_buf[512];
|
|
char hoist[540];
|
|
size_t hoist_len;
|
|
bool already_global;
|
|
bool has_two_tokens = false;
|
|
|
|
/* Check and skip 'uniform' keyword */
|
|
if (strncmp(decl_start, "uniform", 7) == 0
|
|
&& !d3d9_hlsl_is_ident_char(decl_start[7]))
|
|
{
|
|
decl_start += 7;
|
|
while (*decl_start == ' ' || *decl_start == '\t'
|
|
|| *decl_start == '\r' || *decl_start == '\n')
|
|
decl_start++;
|
|
}
|
|
|
|
/* Skip 'in', 'out', 'inout' parameter qualifiers (Cg-specific).
|
|
* If present, this param is an interpolant, not a uniform —
|
|
* keep it in the function signature, don't extract. */
|
|
if (strncmp(decl_start, "inout", 5) == 0
|
|
&& !d3d9_hlsl_is_ident_char(decl_start[5]))
|
|
{
|
|
goto skip_extraction;
|
|
}
|
|
else if (strncmp(decl_start, "in", 2) == 0
|
|
&& !d3d9_hlsl_is_ident_char(decl_start[2]))
|
|
{
|
|
goto skip_extraction;
|
|
}
|
|
else if (strncmp(decl_start, "out", 3) == 0
|
|
&& !d3d9_hlsl_is_ident_char(decl_start[3]))
|
|
{
|
|
goto skip_extraction;
|
|
}
|
|
|
|
/* Check for at least two tokens: skip first ident, skip space,
|
|
* check for second ident */
|
|
{
|
|
const char *tk = decl_start;
|
|
/* Skip first identifier */
|
|
while (tk < decl_end && d3d9_hlsl_is_ident_char(*tk))
|
|
tk++;
|
|
/* Skip whitespace */
|
|
while (tk < decl_end && (*tk == ' ' || *tk == '\t'
|
|
|| *tk == '\r' || *tk == '\n'))
|
|
tk++;
|
|
/* Check for second identifier */
|
|
if (tk < decl_end && d3d9_hlsl_is_ident_char(*tk))
|
|
has_two_tokens = true;
|
|
}
|
|
|
|
if (!has_two_tokens)
|
|
goto skip_extraction;
|
|
|
|
/* Trim trailing whitespace */
|
|
trim = decl_end - 1;
|
|
while (trim > decl_start
|
|
&& (*trim == ' ' || *trim == '\t'
|
|
|| *trim == '\r' || *trim == '\n'))
|
|
trim--;
|
|
decl_len = (size_t)(trim - decl_start + 1);
|
|
|
|
if (decl_len > 0 && decl_len < sizeof(decl_buf) - 1)
|
|
{
|
|
memcpy(decl_buf, decl_start, decl_len);
|
|
decl_buf[decl_len] = '\0';
|
|
|
|
hoist_len = snprintf(hoist, sizeof(hoist),
|
|
"uniform %s;\n", decl_buf);
|
|
|
|
/* Check if already declared globally */
|
|
out[pos] = '\0';
|
|
{
|
|
char decl_semi[520];
|
|
snprintf(decl_semi, sizeof(decl_semi), "%s;", decl_buf);
|
|
already_global = (strstr(out, hoist) != NULL)
|
|
|| (strstr(out, decl_semi) != NULL);
|
|
}
|
|
|
|
if (!already_global)
|
|
{
|
|
while (pos + hoist_len + 1 >= cap)
|
|
{
|
|
char *tmp;
|
|
cap *= 2;
|
|
if (!(tmp = (char*)realloc(out, cap)))
|
|
{ free(out); return NULL; }
|
|
out = tmp;
|
|
}
|
|
memmove(out + last_func_start + hoist_len,
|
|
out + last_func_start, pos - last_func_start);
|
|
memcpy(out + last_func_start, hoist, hoist_len);
|
|
pos += hoist_len;
|
|
last_func_start += hoist_len;
|
|
}
|
|
|
|
/* Remove param from source, handle commas */
|
|
{
|
|
const char *after = decl_end;
|
|
if (*after == ',')
|
|
{
|
|
after++;
|
|
while (*after == ' ' || *after == '\t'
|
|
|| *after == '\r' || *after == '\n')
|
|
after++;
|
|
}
|
|
else if (*after == ')')
|
|
{
|
|
while (pos > 0 && (out[pos-1] == ' '
|
|
|| out[pos-1] == '\t'
|
|
|| out[pos-1] == '\r'
|
|
|| out[pos-1] == '\n'))
|
|
pos--;
|
|
if (pos > 0 && out[pos-1] == ',')
|
|
pos--;
|
|
}
|
|
p = after;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
skip_extraction:
|
|
; /* label requires a statement */
|
|
}
|
|
}
|
|
|
|
/* Regular char */
|
|
if (!d3d9_hlsl_buf_append(&out, &pos, &cap, p, 1))
|
|
goto fail;
|
|
p++;
|
|
}
|
|
|
|
out[pos] = '\0';
|
|
return out;
|
|
|
|
fail:
|
|
free(out);
|
|
return NULL;
|
|
}
|
|
#ifdef _MSC_VER
|
|
#pragma optimize("", on)
|
|
#endif
|
|
|
|
/* Dynamically load D3DCompile and D3DPreprocess with
|
|
* D3DCOMPILE_ENABLE_BACKWARDS_COMPATIBILITY to accept Cg syntax. */
|
|
|
|
typedef HRESULT (WINAPI *PFN_D3DCOMPILE)(
|
|
LPCVOID, SIZE_T, LPCSTR,
|
|
const void*, void*,
|
|
LPCSTR, LPCSTR, UINT, UINT,
|
|
void**, void**);
|
|
|
|
typedef HRESULT (WINAPI *PFN_D3DPREPROCESS)(
|
|
LPCVOID, SIZE_T, LPCSTR,
|
|
const void*, void*,
|
|
void**, void**);
|
|
|
|
#ifndef D3DCOMPILE_ENABLE_BACKWARDS_COMPATIBILITY
|
|
#define D3DCOMPILE_ENABLE_BACKWARDS_COMPATIBILITY (1 << 12)
|
|
#endif
|
|
|
|
#ifndef D3DCOMPILE_OPTIMIZATION_LEVEL3
|
|
#define D3DCOMPILE_OPTIMIZATION_LEVEL3 (1 << 15)
|
|
#endif
|
|
|
|
#ifndef D3DCOMPILE_PREFER_FLOW_CONTROL
|
|
#define D3DCOMPILE_PREFER_FLOW_CONTROL (1 << 10)
|
|
#endif
|
|
|
|
static PFN_D3DCOMPILE d3d9_pCompile = NULL;
|
|
static PFN_D3DPREPROCESS d3d9_pPreprocess = NULL;
|
|
static bool d3d9_compiler_tried = false;
|
|
|
|
static void d3d9_hlsl_ensure_compiler(void)
|
|
{
|
|
HMODULE dll = NULL;
|
|
const char *names[] = {
|
|
"d3dcompiler_47.dll",
|
|
"d3dcompiler_46.dll",
|
|
"d3dcompiler_43.dll",
|
|
NULL
|
|
};
|
|
int i;
|
|
|
|
if (d3d9_compiler_tried)
|
|
return;
|
|
d3d9_compiler_tried = true;
|
|
|
|
for (i = 0; names[i]; i++)
|
|
{
|
|
dll = GetModuleHandleA(names[i]);
|
|
if (!dll)
|
|
dll = LoadLibraryA(names[i]);
|
|
if (dll)
|
|
{
|
|
d3d9_pCompile = (PFN_D3DCOMPILE)GetProcAddress(dll, "D3DCompile");
|
|
d3d9_pPreprocess = (PFN_D3DPREPROCESS)GetProcAddress(dll, "D3DPreprocess");
|
|
if (d3d9_pCompile)
|
|
{
|
|
RARCH_LOG("[D3D9 HLSL] Loaded D3DCompile from %s.\n", names[i]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!d3d9_pCompile)
|
|
RARCH_WARN("[D3D9 HLSL] Could not find D3DCompile, falling back.\n");
|
|
}
|
|
|
|
/* Full Cg→HLSL compilation pipeline:
|
|
* 1. D3DPreprocess to expand all #define macros
|
|
* 2. d3d9_hlsl_fixup_cg_source to fix texture keyword + uniform params
|
|
* 3. D3DCompile with backwards compat flag
|
|
*
|
|
* If D3DPreprocess is unavailable, skips step 1 and tries fixup on the
|
|
* raw source (works for shaders that don't use macros for uniform params). */
|
|
static bool d3d9_hlsl_compile_cg_compat(
|
|
const char *source, size_t source_len,
|
|
const char *source_name,
|
|
const char *entry, const char *target,
|
|
D3DBlob *out_blob)
|
|
{
|
|
D3DBlob error_blob = NULL;
|
|
HRESULT hr;
|
|
|
|
if (!out_blob)
|
|
return false;
|
|
|
|
d3d9_hlsl_ensure_compiler();
|
|
|
|
if (!d3d9_pCompile)
|
|
return d3d_compile(source, source_len, source_name,
|
|
entry, target, out_blob);
|
|
|
|
/* Our own preprocessor already handled #include, #ifdef, #ifndef,
|
|
* #else, #endif. The caller (load_program_from_file_ex) also applied
|
|
* Cg→HLSL fixups, struct semantic insertion, and sampler decomposition,
|
|
* so we compile directly without further source transformations. */
|
|
{
|
|
hr = d3d9_pCompile(
|
|
source, source_len,
|
|
source_name,
|
|
NULL, NULL,
|
|
entry, target,
|
|
D3DCOMPILE_ENABLE_BACKWARDS_COMPATIBILITY,
|
|
0,
|
|
(void**)out_blob,
|
|
(void**)&error_blob);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
/* First attempt failed — retry with maximum optimization and
|
|
* flow-control preference. Complex Cg shaders with many unrolled
|
|
* texture fetches (e.g. 29-tap FIR filters) can exceed SM3.0's
|
|
* temp register limit at default optimization. Level 3 + flow
|
|
* control lets the compiler use loops and reduce register pressure. */
|
|
if (error_blob)
|
|
{
|
|
error_blob->lpVtbl->Release(error_blob);
|
|
error_blob = NULL;
|
|
}
|
|
if (*out_blob)
|
|
{
|
|
(*out_blob)->lpVtbl->Release(*out_blob);
|
|
*out_blob = NULL;
|
|
}
|
|
|
|
hr = d3d9_pCompile(
|
|
source, source_len,
|
|
source_name,
|
|
NULL, NULL,
|
|
entry, target,
|
|
D3DCOMPILE_ENABLE_BACKWARDS_COMPATIBILITY
|
|
| D3DCOMPILE_OPTIMIZATION_LEVEL3
|
|
| D3DCOMPILE_PREFER_FLOW_CONTROL,
|
|
0,
|
|
(void**)out_blob,
|
|
(void**)&error_blob);
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
if (error_blob)
|
|
{
|
|
const char *err_str = (const char*)error_blob->lpVtbl->GetBufferPointer(error_blob);
|
|
RARCH_ERR("[D3D9 HLSL] D3DCompile failed: %s\n", err_str);
|
|
error_blob->lpVtbl->Release(error_blob);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (error_blob)
|
|
error_blob->lpVtbl->Release(error_blob);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/* Ensure all members of the vertex shader output struct are initialized.
|
|
*
|
|
* D3DCompile requires every member of the return struct to be written.
|
|
* Cg allows partial initialization. This pass finds `return OUT;` in
|
|
* main_vertex and inserts `OUT.member = 0;` for any member not assigned
|
|
* elsewhere in the function body.
|
|
*
|
|
* Returns a malloc'd modified copy, or NULL if no changes were made. */
|
|
static char *d3d9_hlsl_init_vs_output_members(const char *source)
|
|
{
|
|
/* Step 1: find 'main_vertex' function and its return type */
|
|
const char *mv = strstr(source, "main_vertex");
|
|
const char *ret_type_start, *ret_type_end;
|
|
char ret_type[128];
|
|
size_t ret_type_len;
|
|
const char *func_body_start, *func_body_end;
|
|
const char *struct_start, *struct_body_start, *struct_body_end;
|
|
|
|
/* Struct member info */
|
|
struct { const char *name; size_t len; } members[32];
|
|
unsigned member_count = 0;
|
|
unsigned unassigned_count = 0;
|
|
char unassigned_buf[2048];
|
|
size_t upos = 0;
|
|
|
|
if (!mv) return NULL;
|
|
|
|
/* Find return type: scan backwards from 'main_vertex' past whitespace */
|
|
ret_type_end = mv;
|
|
while (ret_type_end > source
|
|
&& (ret_type_end[-1] == ' ' || ret_type_end[-1] == '\t'
|
|
|| ret_type_end[-1] == '\n' || ret_type_end[-1] == '\r'))
|
|
ret_type_end--;
|
|
ret_type_start = ret_type_end;
|
|
while (ret_type_start > source && d3d9_hlsl_is_ident_char(ret_type_start[-1]))
|
|
ret_type_start--;
|
|
|
|
ret_type_len = (size_t)(ret_type_end - ret_type_start);
|
|
if (ret_type_len == 0 || ret_type_len >= sizeof(ret_type))
|
|
return NULL;
|
|
memcpy(ret_type, ret_type_start, ret_type_len);
|
|
ret_type[ret_type_len] = '\0';
|
|
|
|
/* Step 2: find the struct definition for the return type */
|
|
{
|
|
char pattern[140];
|
|
snprintf(pattern, sizeof(pattern), "struct %s", ret_type);
|
|
struct_start = strstr(source, pattern);
|
|
if (!struct_start) return NULL;
|
|
/* Verify it's a whole word match */
|
|
{
|
|
const char *after = struct_start + strlen(pattern);
|
|
if (d3d9_hlsl_is_ident_char(*after))
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
struct_body_start = strchr(struct_start, '{');
|
|
if (!struct_body_start) return NULL;
|
|
struct_body_start++;
|
|
|
|
/* Find matching '}' */
|
|
{
|
|
int depth = 1;
|
|
const char *s = struct_body_start;
|
|
while (*s && depth > 0)
|
|
{
|
|
if (*s == '{') depth++;
|
|
else if (*s == '}') depth--;
|
|
s++;
|
|
}
|
|
struct_body_end = s - 1;
|
|
}
|
|
|
|
/* Step 3: parse struct members — extract names */
|
|
{
|
|
const char *mp = struct_body_start;
|
|
while (mp < struct_body_end)
|
|
{
|
|
/* Find each ';' — the member name is the identifier before
|
|
* the optional ': SEMANTIC' before the ';' */
|
|
const char *semi = strchr(mp, ';');
|
|
if (!semi || semi >= struct_body_end) break;
|
|
|
|
/* Scan backwards from ';' past semantic if present */
|
|
{
|
|
const char *end = semi;
|
|
const char *name_end, *name_start;
|
|
|
|
/* Skip past ': SEMANTIC' */
|
|
{
|
|
const char *colon = NULL;
|
|
const char *scan = mp;
|
|
while (scan < semi)
|
|
{
|
|
if (*scan == ':') colon = scan;
|
|
scan++;
|
|
}
|
|
if (colon) end = colon;
|
|
}
|
|
|
|
/* Skip trailing whitespace */
|
|
while (end > mp && (end[-1] == ' ' || end[-1] == '\t')) end--;
|
|
name_end = end;
|
|
|
|
/* Scan backwards to find start of identifier */
|
|
name_start = name_end;
|
|
while (name_start > mp && d3d9_hlsl_is_ident_char(name_start[-1]))
|
|
name_start--;
|
|
|
|
if (name_end > name_start && member_count < 32)
|
|
{
|
|
members[member_count].name = name_start;
|
|
members[member_count].len = (size_t)(name_end - name_start);
|
|
member_count++;
|
|
}
|
|
}
|
|
|
|
mp = semi + 1;
|
|
}
|
|
}
|
|
|
|
if (member_count == 0) return NULL;
|
|
|
|
/* Step 4: find main_vertex function body */
|
|
{
|
|
const char *ob = mv;
|
|
int depth;
|
|
while (*ob && *ob != '{') ob++;
|
|
if (!*ob) return NULL;
|
|
func_body_start = ob + 1;
|
|
depth = 1;
|
|
{
|
|
const char *s = func_body_start;
|
|
while (*s && depth > 0)
|
|
{
|
|
if (*s == '{') depth++;
|
|
else if (*s == '}') depth--;
|
|
s++;
|
|
}
|
|
func_body_end = s;
|
|
}
|
|
}
|
|
|
|
/* Step 5: check for aggregate initialization (OUT = { ... } or
|
|
* ret_type OUT = { ... }). If found, all members are initialized. */
|
|
{
|
|
const char *scan = func_body_start;
|
|
while (scan < func_body_end)
|
|
{
|
|
if (*scan == '=' && scan + 1 < func_body_end)
|
|
{
|
|
const char *after = scan + 1;
|
|
while (*after == ' ' || *after == '\t' || *after == '\n' || *after == '\r')
|
|
after++;
|
|
if (*after == '{')
|
|
{
|
|
/* Check that this is an assignment to the output variable,
|
|
* not some other variable. Look backward for an identifier. */
|
|
const char *before = scan - 1;
|
|
while (before > func_body_start && (*before == ' ' || *before == '\t'))
|
|
before--;
|
|
if (before >= func_body_start && d3d9_hlsl_is_ident_char(*before))
|
|
{
|
|
/* Found identifier = { ... } — assume aggregate init */
|
|
return NULL; /* No changes needed */
|
|
}
|
|
}
|
|
}
|
|
scan++;
|
|
}
|
|
}
|
|
|
|
/* Step 6: check which members are assigned (look for 'OUT.member' or 'OUT .member') */
|
|
{
|
|
unsigned i;
|
|
for (i = 0; i < member_count; i++)
|
|
{
|
|
bool found = false;
|
|
/* Build "OUT.membername" pattern and search within function body */
|
|
/* Try common output variable names: OUT, Out, output */
|
|
{
|
|
const char *scan = func_body_start;
|
|
while (scan < func_body_end)
|
|
{
|
|
/* Look for '.membername' preceded by an identifier */
|
|
if (*scan == '.' && strncmp(scan + 1,
|
|
members[i].name, members[i].len) == 0
|
|
&& !d3d9_hlsl_is_ident_char(scan[1 + members[i].len]))
|
|
{
|
|
/* Check it's an assignment — look for '=' after the member,
|
|
* possibly with a swizzle (.xy, .xyzw, etc.) in between */
|
|
const char *eq = scan + 1 + members[i].len;
|
|
/* Skip optional swizzle: .x, .xy, .xyz, .xyzw, .rg, etc. */
|
|
if (*eq == '.')
|
|
{
|
|
eq++;
|
|
while (d3d9_hlsl_is_ident_char(*eq)) eq++;
|
|
}
|
|
while (*eq == ' ' || *eq == '\t') eq++;
|
|
if (*eq == '=' && eq[1] != '=')
|
|
{ found = true; break; }
|
|
}
|
|
scan++;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
/* This member is not assigned — add initialization */
|
|
size_t added = snprintf(unassigned_buf + upos,
|
|
sizeof(unassigned_buf) - upos,
|
|
"OUT.%.*s = 0;\n",
|
|
(int)members[i].len, members[i].name);
|
|
upos += added;
|
|
unassigned_count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (unassigned_count == 0) return NULL;
|
|
|
|
/* Step 6: insert initializations before 'return OUT;' */
|
|
{
|
|
const char *ret_stmt = strstr(func_body_start, "return ");
|
|
size_t src_len, pre_len, tail_len, new_len;
|
|
char *out;
|
|
|
|
if (!ret_stmt || ret_stmt >= func_body_end) return NULL;
|
|
|
|
src_len = strlen(source);
|
|
pre_len = (size_t)(ret_stmt - source);
|
|
tail_len = src_len - pre_len;
|
|
new_len = src_len + upos;
|
|
|
|
out = (char*)malloc(new_len + 1);
|
|
if (!out) return NULL;
|
|
|
|
memcpy(out, source, pre_len);
|
|
memcpy(out + pre_len, unassigned_buf, upos);
|
|
memcpy(out + pre_len + upos, ret_stmt, tail_len);
|
|
out[new_len] = '\0';
|
|
return out;
|
|
}
|
|
}
|
|
|
|
/* Convert consecutively-invoked single-parameter macros into [loop] for
|
|
* loops. Many Cg shaders unroll filter kernels via macros like:
|
|
*
|
|
* #define macroloop(i) <body using i>
|
|
* macroloop(0)
|
|
* macroloop(1)
|
|
* ...
|
|
* macroloop(28)
|
|
*
|
|
* The Cg compiler handles this, but D3DCompile for SM3.0 often exceeds
|
|
* the 32-temp-register limit on the fully expanded code. Converting
|
|
* back to a real loop lets the HLSL compiler emit loop instructions.
|
|
*
|
|
* Detection:
|
|
* 1. Find '#define NAME(PARAM) BODY' where BODY uses PARAM
|
|
* 2. Find a run of NAME(0) NAME(1) ... NAME(N) with N >= 4
|
|
* 3. Replace the run with '[loop] for (int PARAM = 0; PARAM <= N; PARAM++) { BODY }'
|
|
* 4. Comment out the original #define
|
|
*
|
|
* Returns a malloc'd modified copy, or NULL if no changes were made. */
|
|
static char *d3d9_hlsl_convert_macro_loops(const char *source)
|
|
{
|
|
/* Find #define candidates: single-param function-like macros */
|
|
char macro_name[128];
|
|
char macro_param[64];
|
|
const char *macro_body_start = NULL;
|
|
size_t macro_body_len = 0;
|
|
#ifdef DEBUG
|
|
size_t macro_name_len = 0;
|
|
size_t macro_param_len = 0;
|
|
#endif
|
|
const char *macro_define_start = NULL;
|
|
const char *macro_define_end = NULL;
|
|
const char *p = source;
|
|
while (*p)
|
|
{
|
|
/* Look for #define at start of line */
|
|
if ((p == source || p[-1] == '\n') && *p == '#')
|
|
{
|
|
const char *dir = p + 1;
|
|
while (*dir == ' ' || *dir == '\t') dir++;
|
|
if (strncmp(dir, "define", 6) == 0 && !d3d9_hlsl_is_ident_char(dir[6]))
|
|
{
|
|
const char *nm = dir + 6;
|
|
const char *nm_end, *pp, *pp_end, *body, *body_end;
|
|
|
|
while (*nm == ' ' || *nm == '\t')
|
|
nm++;
|
|
nm_end = nm;
|
|
while (d3d9_hlsl_is_ident_char(*nm_end)) nm_end++;
|
|
|
|
/* Check for (param) */
|
|
if (*nm_end == '(' && nm_end > nm)
|
|
{
|
|
pp = nm_end + 1;
|
|
while (*pp == ' ' || *pp == '\t')
|
|
pp++;
|
|
pp_end = pp;
|
|
while (d3d9_hlsl_is_ident_char(*pp_end)) pp_end++;
|
|
if (pp_end > pp && *pp_end == ')')
|
|
{
|
|
/* Found #define NAME(PARAM) — get the body */
|
|
body = pp_end + 1;
|
|
while (*body == ' ' || *body == '\t')
|
|
body++;
|
|
|
|
/* Find end of body (handle line continuations) */
|
|
body_end = body;
|
|
while (*body_end)
|
|
{
|
|
if (*body_end == '\n')
|
|
{
|
|
if (body_end > body && body_end[-1] == '\\')
|
|
{
|
|
body_end++;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
body_end++;
|
|
}
|
|
|
|
/* Check that PARAM appears in BODY */
|
|
{
|
|
size_t pl = (size_t)(pp_end - pp);
|
|
const char *scan = body;
|
|
bool found = false;
|
|
while (scan < body_end - pl)
|
|
{
|
|
if (strncmp(scan, pp, pl) == 0
|
|
&& (scan == body || !d3d9_hlsl_is_ident_char(scan[-1]))
|
|
&& !d3d9_hlsl_is_ident_char(scan[pl]))
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
scan++;
|
|
}
|
|
|
|
if (found && (size_t)(nm_end - nm) < sizeof(macro_name)
|
|
&& pl < sizeof(macro_param))
|
|
{
|
|
/* Now search for consecutive invocations:
|
|
* NAME(0) NAME(1) ... NAME(N) */
|
|
int max_seq = -1;
|
|
const char *seq_start = NULL, *seq_end_ptr = NULL;
|
|
const char *s = source;
|
|
|
|
while (*s)
|
|
{
|
|
if (strncmp(s, nm, (size_t)(nm_end - nm)) == 0
|
|
&& (s == source || !d3d9_hlsl_is_ident_char(s[-1]))
|
|
&& s[(size_t)(nm_end - nm)] == '(')
|
|
{
|
|
/* Check if this starts a consecutive run from 0 */
|
|
const char *after_name = s + (size_t)(nm_end - nm);
|
|
/* Parse the argument */
|
|
if (after_name[0] == '(' && after_name[1] == '0'
|
|
&& after_name[2] == ')')
|
|
{
|
|
/* Found NAME(0) — scan forward for NAME(1), NAME(2)... */
|
|
int expected = 1;
|
|
const char *run_end = after_name + 3;
|
|
seq_start = s;
|
|
|
|
while (*run_end)
|
|
{
|
|
const char *next = run_end;
|
|
char num_buf[16];
|
|
int num_len;
|
|
while (*next == ' ' || *next == '\t'
|
|
|| *next == '\n' || *next == '\r')
|
|
next++;
|
|
num_len = snprintf(num_buf, sizeof(num_buf),
|
|
"%d", expected);
|
|
if (strncmp(next, nm, (size_t)(nm_end - nm)) == 0
|
|
&& next[(size_t)(nm_end - nm)] == '('
|
|
&& strncmp(next + (size_t)(nm_end - nm) + 1,
|
|
num_buf, num_len) == 0
|
|
&& next[(size_t)(nm_end - nm) + 1 + num_len] == ')')
|
|
{
|
|
run_end = next + (size_t)(nm_end - nm)
|
|
+ 1 + num_len + 1;
|
|
expected++;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (expected > 4)
|
|
{
|
|
max_seq = expected - 1;
|
|
seq_end_ptr = run_end;
|
|
break; /* Use first run found */
|
|
}
|
|
}
|
|
}
|
|
s++;
|
|
}
|
|
|
|
if (max_seq >= 4 && seq_start && seq_end_ptr)
|
|
{
|
|
/* Build the replacement loop */
|
|
size_t cap, opos;
|
|
size_t src_len_full = strlen(source);
|
|
size_t body_l = (size_t)(body_end - body);
|
|
char *out;
|
|
size_t nml = (size_t)(nm_end - nm);
|
|
|
|
memcpy(macro_name, nm, nml);
|
|
macro_name[nml] = '\0';
|
|
#ifdef DEBUG
|
|
macro_name_len = nml;
|
|
#endif
|
|
memcpy(macro_param, pp, (size_t)(pp_end - pp));
|
|
macro_param[(size_t)(pp_end - pp)] = '\0';
|
|
#ifdef DEBUG
|
|
macro_param_len = (size_t)(pp_end - pp);
|
|
#endif
|
|
macro_body_start = body;
|
|
macro_body_len = body_l;
|
|
macro_define_start = p;
|
|
macro_define_end = body_end;
|
|
if (*macro_define_end == '\n')
|
|
macro_define_end++;
|
|
|
|
cap = src_len_full + body_l + 256;
|
|
out = (char*)malloc(cap);
|
|
if (!out)
|
|
return NULL;
|
|
opos = 0;
|
|
|
|
/* Copy everything before the #define, commenting it out */
|
|
{
|
|
size_t pre = (size_t)(macro_define_start - source);
|
|
memcpy(out, source, pre);
|
|
opos = pre;
|
|
}
|
|
|
|
/* Comment out the #define line */
|
|
{
|
|
const char *dl = "/* ";
|
|
size_t dll = 3;
|
|
size_t def_len = (size_t)(macro_define_end - macro_define_start);
|
|
/* Strip any trailing newline from the define for the comment */
|
|
size_t comment_len = def_len;
|
|
while (comment_len > 0
|
|
&& (macro_define_start[comment_len-1] == '\n'
|
|
|| macro_define_start[comment_len-1] == '\r'))
|
|
comment_len--;
|
|
while (opos + dll + comment_len + 10 >= cap)
|
|
{
|
|
char *tmp;
|
|
cap *= 2;
|
|
if (!(tmp = (char*)realloc(out, cap)))
|
|
{ free(out); return NULL; }
|
|
out = tmp;
|
|
}
|
|
memcpy(out + opos, dl, dll); opos += dll;
|
|
memcpy(out + opos, macro_define_start, comment_len);
|
|
opos += comment_len;
|
|
memcpy(out + opos, " */\n", 4); opos += 4;
|
|
}
|
|
|
|
/* Copy between #define and the macro call run */
|
|
{
|
|
size_t mid = (size_t)(seq_start - macro_define_end);
|
|
while (opos + mid + 1 >= cap)
|
|
{
|
|
char *tmp;
|
|
cap *= 2;
|
|
if (!(tmp = (char*)realloc(out, cap)))
|
|
{ free(out); return NULL; }
|
|
out = tmp;
|
|
}
|
|
memcpy(out + opos, macro_define_end, mid);
|
|
opos += mid;
|
|
}
|
|
|
|
/* Emit the [loop] for */
|
|
{
|
|
char loop_header[256];
|
|
size_t lh_len = snprintf(loop_header, sizeof(loop_header),
|
|
"[loop] for (int %s = 0; %s <= %d; %s++) {\n",
|
|
macro_param, macro_param, max_seq, macro_param);
|
|
while (opos + lh_len + 1 >= cap)
|
|
{
|
|
char *tmp;
|
|
cap *= 2;
|
|
if (!(tmp = (char*)realloc(out, cap)))
|
|
{ free(out); return NULL; }
|
|
out = tmp;
|
|
}
|
|
memcpy(out + opos, loop_header, lh_len);
|
|
opos += lh_len;
|
|
}
|
|
|
|
/* Emit the macro body — need to strip line continuations */
|
|
{
|
|
const char *bp_src = macro_body_start;
|
|
while (bp_src < macro_body_start + macro_body_len)
|
|
{
|
|
if (*bp_src == '\\' && bp_src + 1 < macro_body_start + macro_body_len
|
|
&& bp_src[1] == '\n')
|
|
{ bp_src += 2; continue; }
|
|
while (opos + 2 >= cap)
|
|
{
|
|
char *tmp;
|
|
cap *= 2;
|
|
if (!(tmp = (char*)realloc(out, cap)))
|
|
{
|
|
free(out);
|
|
return NULL;
|
|
}
|
|
out = tmp;
|
|
}
|
|
out[opos++] = *bp_src++;
|
|
}
|
|
}
|
|
|
|
/* Close the loop */
|
|
{
|
|
const char *cl = "\n}\n";
|
|
while (opos + 4 >= cap)
|
|
{
|
|
char *tmp;
|
|
cap *= 2;
|
|
if (!(tmp = (char*)realloc(out, cap)))
|
|
{
|
|
free(out);
|
|
return NULL;
|
|
}
|
|
out = tmp;
|
|
}
|
|
memcpy(out + opos, cl, 3); opos += 3;
|
|
}
|
|
|
|
/* Copy everything after the macro call run */
|
|
{
|
|
size_t tail = src_len_full - (size_t)(seq_end_ptr - source);
|
|
while (opos + tail + 1 >= cap)
|
|
{
|
|
char *tmp;
|
|
cap *= 2;
|
|
if (!(tmp = (char*)realloc(out, cap)))
|
|
{
|
|
free(out);
|
|
return NULL;
|
|
}
|
|
out = tmp;
|
|
}
|
|
memcpy(out + opos, seq_end_ptr, tail);
|
|
opos += tail;
|
|
}
|
|
|
|
out[opos] = '\0';
|
|
return out;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
p++;
|
|
}
|
|
|
|
return NULL; /* No changes */
|
|
}
|
|
|
|
/* Load a shader from file AND populate pass_data with CTAB info.
|
|
* Reads the shader source, preprocesses #include directives,
|
|
* fixes Cg-isms, then compiles via d3d_compile. */
|
|
static bool d3d9_hlsl_load_program_from_file_ex(
|
|
LPDIRECT3DDEVICE9 dev,
|
|
struct shader_pass *pass,
|
|
const char *prog,
|
|
hlsl_pass_data_t *pd)
|
|
{
|
|
D3DBlob code_f = NULL;
|
|
D3DBlob code_v = NULL;
|
|
char *source = NULL;
|
|
char *resolved = NULL;
|
|
char base_dir[PATH_MAX_LENGTH];
|
|
size_t src_len = 0;
|
|
|
|
if (!prog || !*prog)
|
|
return false;
|
|
|
|
/* Read source file */
|
|
source = d3d9_hlsl_read_file(prog, &src_len);
|
|
if (!source)
|
|
{
|
|
RARCH_ERR("[D3D9 HLSL] Could not read shader file: %s\n", prog);
|
|
return false;
|
|
}
|
|
|
|
/* Extract directory for #include resolution */
|
|
strlcpy(base_dir, prog, sizeof(base_dir));
|
|
path_basedir(base_dir);
|
|
|
|
/* Resolve #include directives */
|
|
resolved = d3d9_hlsl_preprocess_includes(source, base_dir, 0);
|
|
free(source);
|
|
|
|
if (!resolved)
|
|
{
|
|
RARCH_ERR("[D3D9 HLSL] Include preprocessing failed: %s\n", prog);
|
|
return false;
|
|
}
|
|
|
|
/* Convert Cg '#pragma parameter' + '#define' pairs into HLSL uniforms.
|
|
*
|
|
* Cg shaders declare tweakable parameters like:
|
|
* #pragma parameter AAOFFSET "description" 1.0 0.25 2.0 0.05
|
|
* #define AAOFFSET 1.0
|
|
*
|
|
* The Cg runtime creates a uniform from #pragma parameter, but
|
|
* D3DCompile ignores #pragma and just expands #define — making
|
|
* AAOFFSET a compile-time constant with no CTAB entry.
|
|
*
|
|
* Fix: for each #pragma parameter NAME, find a matching
|
|
* '#define NAME <number>' and replace it with
|
|
* 'uniform float NAME = <number>;' so D3DCompile creates a real
|
|
* uniform that appears in the CTAB and can be set at runtime. */
|
|
{
|
|
char *p = resolved;
|
|
while ((p = strstr(p, "#pragma parameter")) != NULL)
|
|
{
|
|
const char *pp = p + 17; /* skip "#pragma parameter" */
|
|
const char *name_start, *name_end;
|
|
size_t name_len;
|
|
|
|
/* Check that #pragma is at the start of a line (not commented out).
|
|
* Scan backwards to the start of the line and check for '//' */
|
|
{
|
|
const char *ls = p;
|
|
bool in_comment = false;
|
|
while (ls > resolved && ls[-1] != '\n')
|
|
ls--;
|
|
/* Check if there's a '//' between line start and #pragma */
|
|
{
|
|
const char *sc = ls;
|
|
while (sc < p)
|
|
{
|
|
if (sc[0] == '/' && sc[1] == '/')
|
|
{ in_comment = true; break; }
|
|
if (*sc != ' ' && *sc != '\t')
|
|
break;
|
|
sc++;
|
|
}
|
|
}
|
|
if (in_comment)
|
|
{
|
|
p++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* Skip whitespace to get parameter name */
|
|
while (*pp == ' ' || *pp == '\t') pp++;
|
|
name_start = pp;
|
|
while (d3d9_hlsl_is_ident_char(*pp)) pp++;
|
|
name_end = pp;
|
|
name_len = (size_t)(name_end - name_start);
|
|
|
|
if (name_len > 0 && name_len < 128)
|
|
{
|
|
char name_buf[128];
|
|
char define_pattern[140];
|
|
char *def_loc;
|
|
|
|
memcpy(name_buf, name_start, name_len);
|
|
name_buf[name_len] = '\0';
|
|
|
|
/* Build pattern: "#define NAME " (with trailing space) */
|
|
snprintf(define_pattern, sizeof(define_pattern),
|
|
"#define %s ", name_buf);
|
|
|
|
/* Search for matching #define */
|
|
def_loc = strstr(resolved, define_pattern);
|
|
if (!def_loc)
|
|
{
|
|
/* Try with tab separator */
|
|
snprintf(define_pattern, sizeof(define_pattern),
|
|
"#define %s\t", name_buf);
|
|
def_loc = strstr(resolved, define_pattern);
|
|
}
|
|
|
|
if (def_loc)
|
|
{
|
|
/* Found "#define NAME <value>".
|
|
* Extract the value (rest of line). */
|
|
const char *val_start;
|
|
const char *val_end;
|
|
const char *def_line_end;
|
|
char replacement[256];
|
|
size_t def_line_len, repl_len, old_len, tail_len;
|
|
|
|
/* Find end of #define line */
|
|
def_line_end = def_loc;
|
|
while (*def_line_end && *def_line_end != '\n'
|
|
&& *def_line_end != '\r')
|
|
def_line_end++;
|
|
|
|
def_line_len = (size_t)(def_line_end - def_loc);
|
|
|
|
/* Extract value: skip "#define NAME " to get value part */
|
|
val_start = def_loc + strlen(define_pattern);
|
|
/* Trim leading whitespace from value */
|
|
while (val_start < def_line_end
|
|
&& (*val_start == ' ' || *val_start == '\t'))
|
|
val_start++;
|
|
val_end = def_line_end;
|
|
/* Strip // comment from value */
|
|
{
|
|
const char *cmt = val_start;
|
|
while (cmt < val_end - 1)
|
|
{
|
|
if (cmt[0] == '/' && cmt[1] == '/')
|
|
{ val_end = cmt; break; }
|
|
cmt++;
|
|
}
|
|
}
|
|
/* Trim trailing whitespace */
|
|
while (val_end > val_start
|
|
&& (val_end[-1] == ' ' || val_end[-1] == '\t'))
|
|
val_end--;
|
|
|
|
if (val_end > val_start)
|
|
{
|
|
char val_buf[64];
|
|
size_t val_len = (size_t)(val_end - val_start);
|
|
if (val_len >= sizeof(val_buf))
|
|
val_len = sizeof(val_buf) - 1;
|
|
memcpy(val_buf, val_start, val_len);
|
|
val_buf[val_len] = '\0';
|
|
|
|
/* Build replacement: "uniform float NAME = VALUE;" */
|
|
repl_len = snprintf(replacement, sizeof(replacement),
|
|
"uniform float %s = %s;", name_buf, val_buf);
|
|
|
|
/* Replace the #define line in-place */
|
|
old_len = strlen(resolved);
|
|
tail_len = old_len - (size_t)(def_loc - resolved) - def_line_len;
|
|
|
|
if (repl_len <= def_line_len)
|
|
{
|
|
/* Replacement fits — copy in place + shift tail */
|
|
memcpy(def_loc, replacement, repl_len);
|
|
memmove(def_loc + repl_len,
|
|
def_loc + def_line_len, tail_len + 1);
|
|
}
|
|
else
|
|
{
|
|
/* Replacement is longer — realloc */
|
|
size_t new_len = old_len - def_line_len + repl_len;
|
|
size_t def_off = (size_t)(def_loc - resolved);
|
|
char *new_buf = (char*)malloc(new_len + 1);
|
|
if (new_buf)
|
|
{
|
|
memcpy(new_buf, resolved, def_off);
|
|
memcpy(new_buf + def_off, replacement, repl_len);
|
|
memcpy(new_buf + def_off + repl_len,
|
|
resolved + def_off + def_line_len,
|
|
tail_len + 1);
|
|
free(resolved);
|
|
resolved = new_buf;
|
|
/* Reset p to continue scanning in new buffer */
|
|
p = resolved;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Advance past this #pragma line */
|
|
while (*p && *p != '\n') p++;
|
|
if (*p) p++;
|
|
}
|
|
}
|
|
|
|
/* Add semantics to struct members that lack them.
|
|
* Many Cg shaders define structs without HLSL semantics
|
|
* (e.g. tex_coords with float2 members used as interpolants).
|
|
* This pass auto-assigns TEXCOORD semantics. */
|
|
/* Semantic pass */
|
|
/* Add semantics to struct members that lack them */
|
|
{
|
|
char *with_sem = d3d9_hlsl_add_struct_semantics(resolved);
|
|
if (with_sem)
|
|
{
|
|
free(resolved);
|
|
resolved = with_sem;
|
|
}
|
|
}
|
|
|
|
/* Apply Cg→HLSL source-level fixes:
|
|
* 1. 'texture' as identifier → '_texture' (HLSL reserved keyword)
|
|
* 2. 'uniform <type> <name>' in function params → move to global scope
|
|
* (HLSL requires semantics on all VS/PS input params; Cg 'uniform'
|
|
* params are set via the runtime, not from vertex streams) */
|
|
{
|
|
char *fixed = d3d9_hlsl_fixup_cg_source(resolved);
|
|
free(resolved);
|
|
resolved = fixed;
|
|
if (!resolved)
|
|
{
|
|
RARCH_ERR("[D3D9 HLSL] Source fixup failed: %s\n", prog);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* Collapse redundant floatN(X, X, ..., X) constructors where all
|
|
* arguments are identical identifiers. Cg allows float4(float4_var,
|
|
* float4_var, float4_var, float4_var) taking the first N components;
|
|
* HLSL does not. Replace with just (X). */
|
|
{
|
|
const char *search = resolved;
|
|
while ((search = strstr(search, "float")) != NULL)
|
|
{
|
|
const char *fp = search;
|
|
/* Check floatN( pattern */
|
|
if ((fp[5] == '3' || fp[5] == '4')
|
|
&& fp[6] == '('
|
|
&& (fp == resolved || !d3d9_hlsl_is_ident_char(fp[-1])))
|
|
{
|
|
int nc = fp[5] - '0';
|
|
const char *args = fp + 7;
|
|
/* Collect comma-separated args respecting parens */
|
|
const char *a_s[4];
|
|
size_t a_l[4];
|
|
int ac = 0, depth = 1;
|
|
const char *s = args;
|
|
bool ok = true;
|
|
|
|
a_s[0] = s;
|
|
while (*s && depth > 0)
|
|
{
|
|
if (*s == '(') depth++;
|
|
else if (*s == ')') { if (--depth == 0) break; }
|
|
else if (*s == ',' && depth == 1)
|
|
{
|
|
const char *ae = s, *ab = a_s[ac];
|
|
while (ae > ab && (ae[-1] == ' ' || ae[-1] == '\t')) ae--;
|
|
while (ab < ae && (*ab == ' ' || *ab == '\t')) ab++;
|
|
a_s[ac] = ab;
|
|
a_l[ac] = (size_t)(ae - ab);
|
|
ac++;
|
|
if (ac >= nc) { ok = false; break; }
|
|
a_s[ac] = s + 1;
|
|
}
|
|
s++;
|
|
}
|
|
if (ok && depth == 0 && ac == nc - 1)
|
|
{
|
|
/* Record last arg */
|
|
const char *ae = s, *ab = a_s[ac];
|
|
while (ae > ab && (ae[-1] == ' ' || ae[-1] == '\t')) ae--;
|
|
while (ab < ae && (*ab == ' ' || *ab == '\t')) ab++;
|
|
a_s[ac] = ab;
|
|
a_l[ac] = (size_t)(ae - ab);
|
|
ac++;
|
|
|
|
/* Check all args identical and start with letter/underscore */
|
|
if (ac == nc && a_l[0] > 0
|
|
&& d3d9_hlsl_is_ident_char(a_s[0][0])
|
|
&& !(a_s[0][0] >= '0' && a_s[0][0] <= '9'))
|
|
{
|
|
bool same = true;
|
|
int ai;
|
|
for (ai = 1; ai < ac; ai++)
|
|
if (a_l[ai] != a_l[0]
|
|
|| strncmp(a_s[0], a_s[ai], a_l[0]) != 0)
|
|
{ same = false; break; }
|
|
|
|
if (same)
|
|
{
|
|
/* Replace float4(X, X, X, X) with (X) */
|
|
size_t old_len = (size_t)(s + 1 - fp); /* includes closing ) */
|
|
size_t new_len = a_l[0] + 2; /* (X) */
|
|
size_t src_full = strlen(resolved);
|
|
size_t off = (size_t)(fp - resolved);
|
|
size_t tail = src_full - off - old_len;
|
|
char *nb = (char*)malloc(src_full - old_len + new_len + 1);
|
|
if (nb)
|
|
{
|
|
memcpy(nb, resolved, off);
|
|
nb[off] = '(';
|
|
memcpy(nb + off + 1, a_s[0], a_l[0]);
|
|
nb[off + 1 + a_l[0]] = ')';
|
|
memcpy(nb + off + new_len, fp + old_len, tail + 1);
|
|
free(resolved);
|
|
resolved = nb;
|
|
search = resolved + off + new_len;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
search++;
|
|
}
|
|
}
|
|
|
|
/* Decompose sampler members out of structs.
|
|
* HLSL SM3.0 doesn't allow samplers in compound types.
|
|
* This removes 'sampler2D _texture;' from struct bodies and
|
|
* creates standalone sampler declarations for each instance. */
|
|
{
|
|
char *decomposed = d3d9_hlsl_decompose_struct_samplers(resolved);
|
|
if (decomposed)
|
|
{
|
|
free(resolved);
|
|
resolved = decomposed;
|
|
}
|
|
}
|
|
|
|
/* Ensure sampler declarations exist for ORIG/PREV/PASSPREV textures.
|
|
* Multi-pass Cg shaders reference ORIG__texture, PREVn__texture, etc.
|
|
* via compat macros and struct sampler decomposition. If the shader
|
|
* passes these as function parameters (e.g. 'in orig ORIG : TEXCOORD2')
|
|
* rather than global uniforms, the decomposition pass won't create
|
|
* standalone sampler declarations. Scan for references and add
|
|
* missing declarations. */
|
|
{
|
|
static const char *sampler_names[] = {
|
|
"ORIG__texture", "PREV__texture",
|
|
"PREV1__texture", "PREV2__texture", "PREV3__texture",
|
|
"PREV4__texture", "PREV5__texture", "PREV6__texture",
|
|
"PASSPREV1__texture", "PASSPREV2__texture",
|
|
"PASSPREV3__texture", "PASSPREV4__texture",
|
|
NULL
|
|
};
|
|
char decl_buf[1024];
|
|
size_t dpos = 0;
|
|
int si;
|
|
|
|
for (si = 0; sampler_names[si]; si++)
|
|
{
|
|
const char *name = sampler_names[si];
|
|
size_t nlen = strlen(name);
|
|
/* Check if this sampler is actually used in the source.
|
|
* Since ORIG__texture only appears in #define lines (as a macro
|
|
* replacement target), we need to check for uses of the MACROS
|
|
* that reference it: ORIG_texture, ORIG_Sample, ORIG_SamplePoint,
|
|
* or direct uses of ORIG__texture in actual code (not #define). */
|
|
bool found_real_ref = false;
|
|
|
|
/* Check for direct usage outside #define lines */
|
|
{
|
|
const char *ref = resolved;
|
|
while ((ref = strstr(ref, name)) != NULL)
|
|
{
|
|
/* Verify whole-word */
|
|
if ((ref > resolved && d3d9_hlsl_is_ident_char(ref[-1]))
|
|
|| d3d9_hlsl_is_ident_char(ref[nlen]))
|
|
{ ref++; continue; }
|
|
/* Check not inside a #define line */
|
|
{
|
|
const char *ls = ref;
|
|
bool in_define = false;
|
|
while (ls > resolved && ls[-1] != '\n') ls--;
|
|
while (*ls == ' ' || *ls == '\t') ls++;
|
|
if (*ls == '#')
|
|
{
|
|
const char *dir = ls + 1;
|
|
while (*dir == ' ' || *dir == '\t') dir++;
|
|
if (strncmp(dir, "define", 6) == 0)
|
|
in_define = true;
|
|
}
|
|
if (!in_define)
|
|
{ found_real_ref = true; break; }
|
|
}
|
|
ref++;
|
|
}
|
|
}
|
|
|
|
/* Also check for usage of macros that expand to this sampler.
|
|
* E.g., ORIG_texture expands to ORIG__texture, and ORIG_Sample
|
|
* uses ORIG__texture internally via tex2D(ORIG, ...).
|
|
* Check for the prefix (e.g., "ORIG" for "ORIG__texture") used
|
|
* in function parameters like 'in orig ORIG' or calls like
|
|
* ORIG_Sample, ORIG_SamplePoint, ORIG_texture. */
|
|
if (!found_real_ref)
|
|
{
|
|
/* Extract prefix: everything before "__texture" */
|
|
const char *suffix = strstr(name, "__texture");
|
|
if (suffix)
|
|
{
|
|
size_t prefix_len = (size_t)(suffix - name);
|
|
char usage_pattern[64];
|
|
/* Check for 'ORIG_Sample(' or 'ORIG_SamplePoint(' or
|
|
* 'ORIG_texture' used as an identifier in actual code */
|
|
snprintf(usage_pattern, sizeof(usage_pattern),
|
|
"%.*s_Sample", (int)prefix_len, name);
|
|
if (strstr(resolved, usage_pattern))
|
|
found_real_ref = true;
|
|
|
|
if (!found_real_ref)
|
|
{
|
|
snprintf(usage_pattern, sizeof(usage_pattern),
|
|
"%.*s_texture", (int)prefix_len, name);
|
|
/* Check this isn't only in #define lines */
|
|
{
|
|
const char *ref = resolved;
|
|
while ((ref = strstr(ref, usage_pattern)) != NULL)
|
|
{
|
|
const char *ls = ref;
|
|
bool in_define = false;
|
|
while (ls > resolved && ls[-1] != '\n') ls--;
|
|
while (*ls == ' ' || *ls == '\t') ls++;
|
|
if (*ls == '#')
|
|
{
|
|
const char *dir = ls + 1;
|
|
while (*dir == ' ' || *dir == '\t') dir++;
|
|
if (strncmp(dir, "define", 6) == 0)
|
|
in_define = true;
|
|
}
|
|
if (!in_define)
|
|
{ found_real_ref = true; break; }
|
|
ref++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Check for 'in orig ORIG' or 'uniform orig ORIG' pattern
|
|
* in function signatures — struct parameter passing */
|
|
if (!found_real_ref)
|
|
{
|
|
char struct_pattern[64];
|
|
/* For ORIG__texture, check for 'orig ORIG' */
|
|
if (prefix_len == 4 && strncmp(name, "ORIG", 4) == 0)
|
|
snprintf(struct_pattern, sizeof(struct_pattern), "orig ORIG");
|
|
else
|
|
snprintf(struct_pattern, sizeof(struct_pattern),
|
|
"%.*s %.*s", (int)prefix_len, name,
|
|
(int)prefix_len, name);
|
|
if (strstr(resolved, struct_pattern))
|
|
found_real_ref = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!found_real_ref)
|
|
continue;
|
|
/* Check if it's already declared as sampler2D */
|
|
{
|
|
char decl_pattern[192];
|
|
snprintf(decl_pattern, sizeof(decl_pattern),
|
|
"sampler2D %s", name);
|
|
if (strstr(resolved, decl_pattern))
|
|
continue; /* Already declared */
|
|
}
|
|
/* Add declaration */
|
|
dpos += snprintf(decl_buf + dpos, sizeof(decl_buf) - dpos,
|
|
"uniform sampler2D %s;\n", name);
|
|
}
|
|
|
|
if (dpos > 0)
|
|
{
|
|
/* Insert declarations at the beginning of the source */
|
|
size_t old_len = strlen(resolved);
|
|
char *nb = (char*)malloc(old_len + dpos + 1);
|
|
if (nb)
|
|
{
|
|
memcpy(nb, decl_buf, dpos);
|
|
memcpy(nb + dpos, resolved, old_len + 1);
|
|
free(resolved);
|
|
resolved = nb;
|
|
}
|
|
}
|
|
}
|
|
|
|
src_len = strlen(resolved);
|
|
|
|
/* Zero-initialize any unassigned vertex shader output struct members.
|
|
* Cg allows partial struct initialization; HLSL does not. */
|
|
{
|
|
char *inited = d3d9_hlsl_init_vs_output_members(resolved);
|
|
if (inited)
|
|
{
|
|
free(resolved);
|
|
resolved = inited;
|
|
src_len = strlen(resolved);
|
|
}
|
|
}
|
|
|
|
/* Convert consecutively-invoked macros into [loop] for loops.
|
|
* This is critical for shaders with large unrolled filter kernels
|
|
* (e.g. 29-tap FIR) that exceed SM3.0's temp register limit. */
|
|
{
|
|
char *looped = d3d9_hlsl_convert_macro_loops(resolved);
|
|
if (looped)
|
|
{
|
|
free(resolved);
|
|
resolved = looped;
|
|
src_len = strlen(resolved);
|
|
}
|
|
}
|
|
|
|
if (!d3d9_hlsl_compile_cg_compat(resolved, src_len, prog,
|
|
"main_fragment", "ps_3_0", &code_f))
|
|
{
|
|
RARCH_ERR("[D3D9 HLSL] Could not compile fragment shader (%s).\n", prog);
|
|
free(resolved);
|
|
return false;
|
|
}
|
|
if (!d3d9_hlsl_compile_cg_compat(resolved, src_len, prog,
|
|
"main_vertex", "vs_3_0", &code_v))
|
|
{
|
|
RARCH_ERR("[D3D9 HLSL] Could not compile vertex shader (%s).\n", prog);
|
|
code_f->lpVtbl->Release(code_f);
|
|
free(resolved);
|
|
return false;
|
|
}
|
|
|
|
free(resolved);
|
|
|
|
pass->ftable = NULL;
|
|
pass->vtable = NULL;
|
|
|
|
if (pd)
|
|
{
|
|
pd->ps_bytecode = d3d9_hlsl_dup_bytecode(code_f, &pd->ps_bytecode_dwords);
|
|
pd->vs_bytecode = d3d9_hlsl_dup_bytecode(code_v, &pd->vs_bytecode_dwords);
|
|
hlsl_uniform_map_from_bytecode(&pd->vs_map, pd->vs_bytecode, pd->vs_bytecode_dwords);
|
|
hlsl_uniform_map_from_bytecode(&pd->ps_map, pd->ps_bytecode, pd->ps_bytecode_dwords);
|
|
}
|
|
|
|
IDirect3DDevice9_CreatePixelShader(dev,
|
|
(const DWORD*)code_f->lpVtbl->GetBufferPointer(code_f),
|
|
(LPDIRECT3DPIXELSHADER9*)&pass->fprg);
|
|
IDirect3DDevice9_CreateVertexShader(dev,
|
|
(const DWORD*)code_v->lpVtbl->GetBufferPointer(code_v),
|
|
(LPDIRECT3DVERTEXSHADER9*)&pass->vprg);
|
|
code_f->lpVtbl->Release(code_f);
|
|
code_v->lpVtbl->Release(code_v);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* END HLSL RENDERCHAIN */
|
|
|
|
static uint32_t d3d9_hlsl_get_flags(void *data)
|
|
{
|
|
uint32_t flags = 0;
|
|
|
|
BIT32_SET(flags, GFX_CTX_FLAGS_BLACK_FRAME_INSERTION);
|
|
BIT32_SET(flags, GFX_CTX_FLAGS_MENU_FRAME_FILTERING);
|
|
BIT32_SET(flags, GFX_CTX_FLAGS_SHADERS_HLSL);
|
|
BIT32_SET(flags, GFX_CTX_FLAGS_SHADERS_CG);
|
|
|
|
return flags;
|
|
}
|
|
|
|
static void d3d9_hlsl_deinitialize(d3d9_video_t *d3d)
|
|
{
|
|
|
|
hlsl_d3d9_renderchain_free(d3d->renderchain_data);
|
|
|
|
d3d9_vertex_buffer_free(d3d->menu_display.buffer,
|
|
d3d->menu_display.decl);
|
|
|
|
if (d3d9_hlsl_menu_fullscreen_vbo)
|
|
{
|
|
IDirect3DVertexBuffer9_Release(d3d9_hlsl_menu_fullscreen_vbo);
|
|
d3d9_hlsl_menu_fullscreen_vbo = NULL;
|
|
}
|
|
if (d3d9_hlsl_pipeline_decl)
|
|
{
|
|
IDirect3DVertexDeclaration9_Release(d3d9_hlsl_pipeline_decl);
|
|
d3d9_hlsl_pipeline_decl = NULL;
|
|
}
|
|
if (d3d9_hlsl_menu_pipeline_vbo)
|
|
{
|
|
IDirect3DVertexBuffer9_Release(d3d9_hlsl_menu_pipeline_vbo);
|
|
d3d9_hlsl_menu_pipeline_vbo = NULL;
|
|
}
|
|
|
|
if (d3d9_hlsl_overlay_decl)
|
|
{
|
|
IDirect3DVertexDeclaration9_Release(d3d9_hlsl_overlay_decl);
|
|
d3d9_hlsl_overlay_decl = NULL;
|
|
}
|
|
|
|
d3d->renderchain_data = NULL;
|
|
d3d->menu_display.buffer = NULL;
|
|
d3d->menu_display.decl = NULL;
|
|
}
|
|
|
|
static bool d3d9_hlsl_init_base(
|
|
d3d9_video_t *d3d, const video_info_t *info)
|
|
{
|
|
D3DPRESENT_PARAMETERS d3dpp;
|
|
#ifndef _XBOX
|
|
HWND focus_window = win32_get_window();
|
|
#endif
|
|
|
|
memset(&d3dpp, 0, sizeof(d3dpp));
|
|
|
|
d3d->d3d9 = (LPDIRECT3D9)d3d9_create();
|
|
|
|
/* this needs d3d->d3d9 created first */
|
|
d3d9_make_d3dpp(d3d, info, &d3dpp);
|
|
|
|
if (!d3d->d3d9)
|
|
return false;
|
|
if (!d3d9_create_device(&d3d->dev, &d3dpp,
|
|
d3d->d3d9,
|
|
focus_window,
|
|
d3d->cur_mon_id)
|
|
)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static void d3d9_hlsl_log_info(const struct LinkInfo *info)
|
|
{
|
|
RARCH_LOG("[D3D9] Render pass info:\n");
|
|
RARCH_LOG("\tTexture width: %u\n", info->tex_w);
|
|
RARCH_LOG("\tTexture height: %u\n", info->tex_h);
|
|
|
|
RARCH_LOG("\tScale type (X): ");
|
|
|
|
switch (info->pass->fbo.type_x)
|
|
{
|
|
case RARCH_SCALE_INPUT:
|
|
RARCH_LOG("Relative @ %fx\n", info->pass->fbo.scale_x);
|
|
break;
|
|
|
|
case RARCH_SCALE_VIEWPORT:
|
|
RARCH_LOG("Viewport @ %fx\n", info->pass->fbo.scale_x);
|
|
break;
|
|
|
|
case RARCH_SCALE_ABSOLUTE:
|
|
RARCH_LOG("Absolute @ %u px\n", info->pass->fbo.abs_x);
|
|
break;
|
|
}
|
|
|
|
RARCH_LOG("\tScale type (Y): ");
|
|
|
|
switch (info->pass->fbo.type_y)
|
|
{
|
|
case RARCH_SCALE_INPUT:
|
|
RARCH_LOG("Relative @ %fx\n", info->pass->fbo.scale_y);
|
|
break;
|
|
|
|
case RARCH_SCALE_VIEWPORT:
|
|
RARCH_LOG("Viewport @ %fx\n", info->pass->fbo.scale_y);
|
|
break;
|
|
|
|
case RARCH_SCALE_ABSOLUTE:
|
|
RARCH_LOG("Absolute @ %u px\n", info->pass->fbo.abs_y);
|
|
break;
|
|
}
|
|
|
|
RARCH_LOG("\tBilinear filter: %s\n",
|
|
info->pass->filter == RARCH_FILTER_LINEAR ? "true" : "false");
|
|
}
|
|
|
|
static bool d3d9_init_multipass(d3d9_video_t *d3d, const char *shader_path)
|
|
{
|
|
unsigned i;
|
|
struct video_shader_pass *pass = NULL;
|
|
|
|
memset(&d3d->shader, 0, sizeof(d3d->shader));
|
|
|
|
if (!video_shader_load_preset_into_shader(shader_path, &d3d->shader))
|
|
return false;
|
|
|
|
RARCH_LOG("[D3D9] Found %u shaders.\n", d3d->shader.passes);
|
|
|
|
for (i = 0; i < d3d->shader.passes; i++)
|
|
{
|
|
if (d3d->shader.pass[i].fbo.flags & FBO_SCALE_FLAG_VALID)
|
|
continue;
|
|
|
|
d3d->shader.pass[i].fbo.scale_y = 1.0f;
|
|
d3d->shader.pass[i].fbo.scale_x = 1.0f;
|
|
d3d->shader.pass[i].fbo.type_x = RARCH_SCALE_INPUT;
|
|
d3d->shader.pass[i].fbo.type_y = RARCH_SCALE_INPUT;
|
|
}
|
|
|
|
if (d3d->shader.passes < GFX_MAX_SHADERS &&
|
|
(d3d->shader.pass[d3d->shader.passes - 1].fbo.flags & FBO_SCALE_FLAG_VALID))
|
|
{
|
|
d3d->shader.passes++;
|
|
pass = (struct video_shader_pass*)
|
|
&d3d->shader.pass[d3d->shader.passes - 1];
|
|
|
|
pass->fbo.scale_x = 1.0f;
|
|
pass->fbo.scale_y = 1.0f;
|
|
pass->fbo.type_x = RARCH_SCALE_VIEWPORT;
|
|
pass->fbo.type_y = RARCH_SCALE_VIEWPORT;
|
|
pass->filter = RARCH_FILTER_UNSPEC;
|
|
}
|
|
else
|
|
{
|
|
pass = (struct video_shader_pass*)
|
|
&d3d->shader.pass[d3d->shader.passes - 1];
|
|
|
|
pass->fbo.scale_x = 1.0f;
|
|
pass->fbo.scale_y = 1.0f;
|
|
pass->fbo.type_x = RARCH_SCALE_VIEWPORT;
|
|
pass->fbo.type_y = RARCH_SCALE_VIEWPORT;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool d3d9_hlsl_process_shader(d3d9_video_t *d3d)
|
|
{
|
|
struct video_shader_pass *pass = NULL;
|
|
const char *shader_path = d3d->shader_path;
|
|
if (shader_path && *shader_path)
|
|
return d3d9_init_multipass(d3d, shader_path);
|
|
|
|
memset(&d3d->shader, 0, sizeof(d3d->shader));
|
|
d3d->shader.passes = 1;
|
|
|
|
pass = (struct video_shader_pass*)&d3d->shader.pass[0];
|
|
|
|
pass->fbo.flags |= FBO_SCALE_FLAG_VALID;
|
|
pass->fbo.scale_y = 1.0;
|
|
pass->fbo.type_y = RARCH_SCALE_VIEWPORT;
|
|
pass->fbo.scale_x = pass->fbo.scale_y;
|
|
pass->fbo.type_x = pass->fbo.type_y;
|
|
|
|
if (d3d->shader_path && *d3d->shader_path)
|
|
strlcpy(pass->source.path, d3d->shader_path,
|
|
sizeof(pass->source.path));
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool d3d9_hlsl_init_chain(d3d9_video_t *d3d,
|
|
unsigned input_scale, bool rgb32)
|
|
{
|
|
struct LinkInfo link_info;
|
|
#ifndef _XBOX
|
|
unsigned current_width, current_height, out_width, out_height;
|
|
#endif
|
|
unsigned i = 0;
|
|
settings_t *settings = config_get_ptr();
|
|
bool video_smooth = settings->bools.video_smooth;
|
|
|
|
/* Setup information for first pass. */
|
|
link_info.tex_w = input_scale * RARCH_SCALE_BASE;
|
|
link_info.tex_h = input_scale * RARCH_SCALE_BASE;
|
|
link_info.pass = &d3d->shader.pass[0];
|
|
|
|
{
|
|
hlsl_renderchain_t *renderchain =
|
|
(hlsl_renderchain_t*)calloc(1, sizeof(*renderchain));
|
|
if (!renderchain)
|
|
return false;
|
|
|
|
renderchain->chain.passes = shader_pass_vector_list_new();
|
|
renderchain->chain.luts = lut_info_vector_list_new();
|
|
renderchain->chain.bound_tex = unsigned_vector_list_new();
|
|
renderchain->chain.bound_vert = unsigned_vector_list_new();
|
|
|
|
d3d->renderchain_data = renderchain;
|
|
}
|
|
|
|
RARCH_LOG("[D3D9 HLSL] Using HLSL shader backend.\n");
|
|
|
|
if (
|
|
!hlsl_d3d9_renderchain_init(
|
|
d3d, (hlsl_renderchain_t*)d3d->renderchain_data,
|
|
d3d->dev, &d3d->out_vp, &link_info,
|
|
rgb32
|
|
? RETRO_PIXEL_FORMAT_XRGB8888
|
|
: RETRO_PIXEL_FORMAT_RGB565
|
|
)
|
|
)
|
|
return false;
|
|
|
|
d3d9_hlsl_log_info(&link_info);
|
|
|
|
#ifndef _XBOX
|
|
current_width = link_info.tex_w;
|
|
current_height = link_info.tex_h;
|
|
out_width = 0;
|
|
out_height = 0;
|
|
|
|
for (i = 1; i < d3d->shader.passes; i++)
|
|
{
|
|
switch (link_info.pass->fbo.type_x)
|
|
{
|
|
case RARCH_SCALE_VIEWPORT:
|
|
out_width = link_info.pass->fbo.scale_x * d3d->out_vp.Width;
|
|
break;
|
|
case RARCH_SCALE_ABSOLUTE:
|
|
out_width = link_info.pass->fbo.abs_x;
|
|
break;
|
|
case RARCH_SCALE_INPUT:
|
|
out_width = link_info.pass->fbo.scale_x * current_width;
|
|
break;
|
|
}
|
|
switch (link_info.pass->fbo.type_y)
|
|
{
|
|
case RARCH_SCALE_VIEWPORT:
|
|
out_height = link_info.pass->fbo.scale_y * d3d->out_vp.Height;
|
|
break;
|
|
case RARCH_SCALE_ABSOLUTE:
|
|
out_height = link_info.pass->fbo.abs_y;
|
|
break;
|
|
case RARCH_SCALE_INPUT:
|
|
out_height = link_info.pass->fbo.scale_y * current_height;
|
|
break;
|
|
}
|
|
|
|
link_info.pass = &d3d->shader.pass[i];
|
|
link_info.tex_w = next_pow2(out_width);
|
|
link_info.tex_h = next_pow2(out_height);
|
|
|
|
current_width = out_width;
|
|
current_height = out_height;
|
|
|
|
if (!hlsl_d3d9_renderchain_add_pass(
|
|
(hlsl_renderchain_t*)d3d->renderchain_data, &link_info))
|
|
{
|
|
RARCH_ERR("[D3D9 HLSL] Failed to add pass.\n");
|
|
return false;
|
|
}
|
|
d3d9_hlsl_log_info(&link_info);
|
|
}
|
|
#endif
|
|
|
|
{
|
|
hlsl_renderchain_t *_chain = (hlsl_renderchain_t*)d3d->renderchain_data;
|
|
d3d9_hlsl_renderchain_t *chain = (d3d9_hlsl_renderchain_t*)&_chain->chain;
|
|
|
|
for (i = 0; i < d3d->shader.luts; i++)
|
|
{
|
|
if (!d3d9_hlsl_renderchain_add_lut(
|
|
chain,
|
|
d3d->shader.lut[i].id,
|
|
d3d->shader.lut[i].path,
|
|
d3d->shader.lut[i].filter == RARCH_FILTER_UNSPEC
|
|
? video_smooth
|
|
: (d3d->shader.lut[i].filter == RARCH_FILTER_LINEAR)))
|
|
{
|
|
RARCH_ERR("[D3D9 HLSL] Failed to init LUTs.\n");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void d3d9_set_font_rect(
|
|
d3d9_video_t *d3d,
|
|
const struct font_params *params)
|
|
{
|
|
settings_t *settings = config_get_ptr();
|
|
float pos_x = settings->floats.video_msg_pos_x;
|
|
float pos_y = settings->floats.video_msg_pos_y;
|
|
float font_size = settings->floats.video_font_size;
|
|
|
|
if (params)
|
|
{
|
|
pos_x = params->x;
|
|
pos_y = params->y;
|
|
font_size *= params->scale;
|
|
}
|
|
|
|
d3d->font_rect.left = d3d->video_info.width * pos_x;
|
|
d3d->font_rect.right = d3d->video_info.width;
|
|
d3d->font_rect.top = (1.0f - pos_y) * d3d->video_info.height - font_size;
|
|
d3d->font_rect.bottom = d3d->video_info.height;
|
|
|
|
d3d->font_rect_shifted = d3d->font_rect;
|
|
d3d->font_rect_shifted.left -= 2;
|
|
d3d->font_rect_shifted.right -= 2;
|
|
d3d->font_rect_shifted.top += 2;
|
|
d3d->font_rect_shifted.bottom += 2;
|
|
}
|
|
|
|
static void d3d9_hlsl_set_viewport(void *data,
|
|
unsigned width, unsigned height,
|
|
bool force_full,
|
|
bool allow_rotate)
|
|
{
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
float translate_x = d3d->translate_x;
|
|
float translate_y = d3d->translate_y;
|
|
int x = 0;
|
|
int y = 0;
|
|
struct video_viewport vp;
|
|
|
|
/* Width/height parameters are intentionally overwritten here:
|
|
* the caller's values are not used (pre-existing behaviour). */
|
|
width = d3d->vp.full_width;
|
|
height = d3d->vp.full_height;
|
|
|
|
vp.full_width = width;
|
|
vp.full_height = height;
|
|
video_driver_update_viewport(&vp, force_full, d3d->keep_aspect, true);
|
|
|
|
x = vp.x;
|
|
y = vp.y;
|
|
width = vp.width;
|
|
height = vp.height;
|
|
|
|
/* D3D doesn't support negative X/Y viewports ... */
|
|
if (x < 0)
|
|
{
|
|
if (!force_full)
|
|
d3d->translate_x = x * 2;
|
|
x = 0;
|
|
}
|
|
else if (!force_full)
|
|
d3d->translate_x = 0;
|
|
|
|
if (y < 0)
|
|
{
|
|
if (!force_full)
|
|
d3d->translate_y = y * 2;
|
|
y = 0;
|
|
}
|
|
else if (!force_full)
|
|
d3d->translate_y = 0;
|
|
|
|
if (!force_full)
|
|
{
|
|
if (translate_x != d3d->translate_x || translate_y != d3d->translate_y)
|
|
d3d->needs_restore = true;
|
|
}
|
|
|
|
d3d->out_vp.X = x;
|
|
d3d->out_vp.Y = y;
|
|
d3d->out_vp.Width = width;
|
|
d3d->out_vp.Height = height;
|
|
d3d->out_vp.MinZ = 0.0f;
|
|
d3d->out_vp.MaxZ = 1.0f;
|
|
|
|
d3d9_set_font_rect(d3d, NULL);
|
|
}
|
|
|
|
static void d3d9_hlsl_set_osd_msg(void *data,
|
|
const char *msg, size_t msg_len,
|
|
const struct font_params *params, void *font)
|
|
{
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
LPDIRECT3DDEVICE9 dev = d3d->dev;
|
|
|
|
d3d9_set_font_rect(d3d, params);
|
|
IDirect3DDevice9_BeginScene(dev);
|
|
font_driver_render_msg(d3d, msg, msg_len, params, font);
|
|
IDirect3DDevice9_EndScene(dev);
|
|
}
|
|
|
|
static bool d3d9_hlsl_initialize(
|
|
d3d9_video_t *d3d, const video_info_t *info)
|
|
{
|
|
bool ret = true;
|
|
|
|
if (!d3d->d3d9)
|
|
ret = d3d9_hlsl_init_base(d3d, info);
|
|
else if (d3d->needs_restore)
|
|
{
|
|
D3DPRESENT_PARAMETERS d3dpp;
|
|
|
|
d3d9_make_d3dpp(d3d, info, &d3dpp);
|
|
if (!d3d9_reset(d3d->dev, &d3dpp))
|
|
{
|
|
d3d9_hlsl_deinitialize(d3d);
|
|
IDirect3D9_Release(d3d->d3d9);
|
|
d3d->d3d9 = NULL;
|
|
|
|
ret = d3d9_hlsl_init_base(d3d, info);
|
|
if (ret)
|
|
RARCH_LOG("[D3D9 HLSL] Recovered from dead state.\n");
|
|
}
|
|
|
|
#ifdef HAVE_MENU
|
|
menu_driver_init(info->is_threaded);
|
|
#endif
|
|
}
|
|
|
|
if (!ret)
|
|
return ret;
|
|
|
|
if (!d3d9_hlsl_init_chain(d3d, info->input_scale, info->rgb32))
|
|
{
|
|
RARCH_ERR("[D3D9 HLSL] Failed to initialize render chain.\n");
|
|
return false;
|
|
}
|
|
|
|
/* d3d->vp.full_* was written by the caller (d3d9_hlsl_init_internal
|
|
* has already called set_size at this point). */
|
|
d3d9_hlsl_set_viewport(d3d,
|
|
d3d->vp.full_width, d3d->vp.full_height, false, true);
|
|
|
|
|
|
{
|
|
static const D3DVERTEXELEMENT9 VertexElements[4] = {
|
|
{0, offsetof(Vertex, x),
|
|
D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,
|
|
D3DDECLUSAGE_POSITION,0},
|
|
{0, offsetof(Vertex, u),
|
|
D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT,
|
|
D3DDECLUSAGE_TEXCOORD,0},
|
|
{0, offsetof(Vertex, color),
|
|
D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT,
|
|
D3DDECLUSAGE_COLOR, 0},
|
|
D3DDECL_END()
|
|
};
|
|
if (!SUCCEEDED(IDirect3DDevice9_CreateVertexDeclaration(d3d->dev,
|
|
(const D3DVERTEXELEMENT9*)VertexElements, (IDirect3DVertexDeclaration9**)&d3d->menu_display.decl)))
|
|
return false;
|
|
}
|
|
|
|
d3d->menu_display.offset = 0;
|
|
d3d->menu_display.size = 8192;
|
|
d3d->menu_display.buffer = NULL;
|
|
if (!SUCCEEDED(IDirect3DDevice9_CreateVertexBuffer(
|
|
d3d->dev,
|
|
d3d->menu_display.size * sizeof(Vertex),
|
|
D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, 0,
|
|
D3DPOOL_DEFAULT,
|
|
(LPDIRECT3DVERTEXBUFFER9*)&d3d->menu_display.buffer, NULL)))
|
|
d3d->menu_display.buffer = NULL;
|
|
|
|
if (!d3d->menu_display.buffer)
|
|
return false;
|
|
|
|
/* Create a 1x1 white fallback texture for draws with no texture
|
|
* (e.g. widget background quads when gfx_white_texture is not loaded) */
|
|
if (!d3d9_hlsl_white_texture)
|
|
{
|
|
IDirect3DDevice9_CreateTexture(d3d->dev, 1, 1, 1,
|
|
0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED,
|
|
(LPDIRECT3DTEXTURE9*)&d3d9_hlsl_white_texture, NULL);
|
|
if (d3d9_hlsl_white_texture)
|
|
{
|
|
D3DLOCKED_RECT lr;
|
|
if (SUCCEEDED(IDirect3DTexture9_LockRect(
|
|
d3d9_hlsl_white_texture, 0, &lr, NULL, 0)))
|
|
{
|
|
*((DWORD*)lr.pBits) = 0xFFFFFFFF; /* ARGB white */
|
|
IDirect3DTexture9_UnlockRect(d3d9_hlsl_white_texture, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Pre-computed D3D left-handed orthographic projection (0,1,0,1,0,1) */
|
|
{
|
|
static const math_matrix_4x4 k_ortho = {{
|
|
2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, -1, -1, 0, 1
|
|
}};
|
|
d3d->mvp_transposed = k_ortho;
|
|
d3d->mvp = k_ortho;
|
|
}
|
|
|
|
if (d3d->translate_x)
|
|
{
|
|
float vp_x = -(d3d->translate_x/(float)d3d->out_vp.Width);
|
|
MAT_ELEM_4X4(d3d->mvp, 0, 3) = -1.0f + vp_x - 2.0f * 1 / (0 - 1);
|
|
}
|
|
|
|
if (d3d->translate_y)
|
|
{
|
|
float vp_y = -(d3d->translate_y/(float)d3d->out_vp.Height);
|
|
MAT_ELEM_4X4(d3d->mvp, 1, 3) = 1.0f + vp_y + 2.0f * 1 / (0 - 1);
|
|
}
|
|
|
|
IDirect3DDevice9_SetRenderState(d3d->dev, D3DRS_CULLMODE, D3DCULL_NONE);
|
|
IDirect3DDevice9_SetRenderState(d3d->dev, D3DRS_SCISSORTESTENABLE, TRUE);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool d3d9_hlsl_restore(d3d9_video_t *d3d)
|
|
{
|
|
d3d9_hlsl_deinitialize(d3d);
|
|
|
|
if (!d3d9_hlsl_initialize(d3d, &d3d->video_info))
|
|
{
|
|
RARCH_ERR("[D3D9 HLSL] Restore error.\n");
|
|
return false;
|
|
}
|
|
|
|
d3d->needs_restore = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool d3d9_hlsl_set_shader(void *data,
|
|
enum rarch_shader_type type, const char *path)
|
|
{
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
|
|
if (!d3d)
|
|
return false;
|
|
|
|
if ((d3d->shader_path && *d3d->shader_path))
|
|
free(d3d->shader_path);
|
|
d3d->shader_path = NULL;
|
|
|
|
switch (type)
|
|
{
|
|
case RARCH_SHADER_CG:
|
|
case RARCH_SHADER_HLSL:
|
|
if (path && *path)
|
|
d3d->shader_path = strdup(path);
|
|
|
|
break;
|
|
case RARCH_SHADER_NONE:
|
|
break;
|
|
default:
|
|
RARCH_WARN("[D3D9 HLSL] Only CG shaders are supported. Falling back to stock.\n");
|
|
}
|
|
|
|
if (!d3d9_hlsl_process_shader(d3d) || !d3d9_hlsl_restore(d3d))
|
|
{
|
|
RARCH_ERR("[D3D9 HLSL] Failed to set shader.\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool d3d9_hlsl_init_internal(d3d9_video_t *d3d,
|
|
const video_info_t *info, input_driver_t **input,
|
|
void **input_data)
|
|
{
|
|
#ifdef HAVE_MONITOR
|
|
bool windowed_full;
|
|
RECT mon_rect;
|
|
MONITORINFOEX current_mon;
|
|
HMONITOR hm_to_use;
|
|
#endif
|
|
unsigned full_x = 0;
|
|
unsigned full_y = 0;
|
|
settings_t *settings = config_get_ptr();
|
|
overlay_t *menu = (overlay_t*)calloc(1, sizeof(*menu));
|
|
|
|
if (!menu)
|
|
return false;
|
|
|
|
d3d->menu = menu;
|
|
d3d->cur_mon_id = 0;
|
|
d3d->menu->tex_coords[0] = 0;
|
|
d3d->menu->tex_coords[1] = 0;
|
|
d3d->menu->tex_coords[2] = 1;
|
|
d3d->menu->tex_coords[3] = 1;
|
|
d3d->menu->vert_coords[0] = 0;
|
|
d3d->menu->vert_coords[1] = 1;
|
|
d3d->menu->vert_coords[2] = 1;
|
|
d3d->menu->vert_coords[3] = -1;
|
|
|
|
#ifdef HAVE_WINDOW
|
|
memset(&d3d->windowClass, 0, sizeof(d3d->windowClass));
|
|
d3d->windowClass.lpfnWndProc = wnd_proc_d3d_common;
|
|
#ifdef HAVE_DINPUT
|
|
if (string_is_equal(settings->arrays.input_driver, "dinput"))
|
|
d3d->windowClass.lpfnWndProc = wnd_proc_d3d_dinput;
|
|
#endif
|
|
#ifdef HAVE_WINRAWINPUT
|
|
if (string_is_equal(settings->arrays.input_driver, "raw"))
|
|
d3d->windowClass.lpfnWndProc = wnd_proc_d3d_winraw;
|
|
#endif
|
|
win32_window_init(&d3d->windowClass, true, NULL);
|
|
#endif
|
|
|
|
#ifdef HAVE_MONITOR
|
|
win32_monitor_info(¤t_mon, &hm_to_use, &d3d->cur_mon_id);
|
|
|
|
mon_rect = current_mon.rcMonitor;
|
|
g_win32_resize_width = info->width;
|
|
g_win32_resize_height = info->height;
|
|
|
|
windowed_full = settings->bools.video_windowed_fullscreen;
|
|
|
|
full_x = (windowed_full || info->width == 0)
|
|
? (unsigned)(mon_rect.right - mon_rect.left)
|
|
: info->width;
|
|
full_y = (windowed_full || info->height == 0)
|
|
? (unsigned)(mon_rect.bottom - mon_rect.top)
|
|
: info->height;
|
|
#else
|
|
d3d9_get_video_size(d3d, &full_x, &full_y);
|
|
#endif
|
|
{
|
|
unsigned new_width = info->fullscreen ? full_x : info->width;
|
|
unsigned new_height = info->fullscreen ? full_y : info->height;
|
|
video_driver_set_output_size(new_width, new_height);
|
|
d3d->vp.full_width = new_width;
|
|
d3d->vp.full_height = new_height;
|
|
|
|
#ifdef HAVE_WINDOW
|
|
/* Use new_width / new_height directly rather than reading
|
|
* them back via video_driver_get_output_size: nothing in the
|
|
* codebase writes video_st->width / height between the
|
|
* set_size above and this call except us. */
|
|
if (!win32_set_video_mode(d3d, new_width, new_height,
|
|
info->fullscreen))
|
|
{
|
|
RARCH_ERR("[D3D9 HLSL] win32_set_video_mode failed.\n");
|
|
return false;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
d3d->video_info = *info;
|
|
|
|
if (!d3d9_hlsl_initialize(d3d, &d3d->video_info))
|
|
return false;
|
|
|
|
d3d_input_driver(settings->arrays.input_joypad_driver,
|
|
settings->arrays.input_joypad_driver, input, input_data);
|
|
|
|
{
|
|
char version_str[128];
|
|
D3DADAPTER_IDENTIFIER9 ident = {0};
|
|
|
|
IDirect3D9_GetAdapterIdentifier(d3d->d3d9, 0, 0, &ident);
|
|
|
|
version_str[0] = '\0';
|
|
|
|
snprintf(version_str, sizeof(version_str), "%u.%u.%u.%u",
|
|
HIWORD(ident.DriverVersion.HighPart),
|
|
LOWORD(ident.DriverVersion.HighPart),
|
|
HIWORD(ident.DriverVersion.LowPart),
|
|
LOWORD(ident.DriverVersion.LowPart));
|
|
|
|
RARCH_LOG("[D3D9 HLSL] Using GPU: \"%s\".\n", ident.Description);
|
|
RARCH_LOG("[D3D9 HLSL] GPU API Version: %s.\n", version_str);
|
|
video_driver_set_gpu_api_version_string(version_str);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void *d3d9_hlsl_init(const video_info_t *info,
|
|
input_driver_t **input, void **input_data)
|
|
{
|
|
d3d9_video_t *d3d = (d3d9_video_t*)calloc(1, sizeof(*d3d));
|
|
|
|
if (!d3d)
|
|
return NULL;
|
|
if (!d3d9_initialize_symbols(GFX_CTX_DIRECT3D9_API))
|
|
goto error;
|
|
|
|
#ifndef _XBOX
|
|
win32_window_reset();
|
|
win32_monitor_init();
|
|
#endif
|
|
|
|
/* Default values */
|
|
d3d->dev = NULL;
|
|
d3d->dev_rotation = 0;
|
|
d3d->needs_restore = false;
|
|
#ifdef HAVE_OVERLAY
|
|
d3d->overlays_enabled = false;
|
|
#endif
|
|
d3d->should_resize = false;
|
|
d3d->menu = NULL;
|
|
|
|
if (!d3d9_hlsl_init_internal(d3d, info, input, input_data))
|
|
goto error;
|
|
|
|
d3d->keep_aspect = info->force_aspect;
|
|
|
|
return d3d;
|
|
|
|
error:
|
|
RARCH_ERR("[D3D9 HLSL] Failed to init D3D.\n");
|
|
free(d3d);
|
|
return NULL;
|
|
}
|
|
|
|
static void d3d9_hlsl_viewport_info(void *data, struct video_viewport *vp)
|
|
{
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
|
|
vp->x = d3d->out_vp.X;
|
|
vp->y = d3d->out_vp.Y;
|
|
vp->width = d3d->out_vp.Width;
|
|
vp->height = d3d->out_vp.Height;
|
|
|
|
vp->full_width = d3d->vp.full_width;
|
|
vp->full_height = d3d->vp.full_height;
|
|
}
|
|
|
|
#ifdef HAVE_OVERLAY
|
|
static void d3d9_hlsl_free_overlays(d3d9_video_t *d3d)
|
|
{
|
|
unsigned i;
|
|
|
|
for (i = 0; i < d3d->overlays_size; i++)
|
|
{
|
|
if ((LPDIRECT3DTEXTURE9)d3d->overlays[i].tex)
|
|
IDirect3DTexture9_Release((LPDIRECT3DTEXTURE9)d3d->overlays[i].tex);
|
|
d3d9_vertex_buffer_free(d3d->overlays[i].vert_buf, NULL);
|
|
}
|
|
free(d3d->overlays);
|
|
d3d->overlays = NULL;
|
|
d3d->overlays_size = 0;
|
|
}
|
|
|
|
static void d3d9_hlsl_overlay_tex_geom(
|
|
void *data,
|
|
unsigned index,
|
|
float x, float y,
|
|
float w, float h)
|
|
{
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
|
|
if (!d3d)
|
|
return;
|
|
|
|
d3d->overlays[index].tex_coords[0] = x;
|
|
d3d->overlays[index].tex_coords[1] = y;
|
|
d3d->overlays[index].tex_coords[2] = w;
|
|
d3d->overlays[index].tex_coords[3] = h;
|
|
}
|
|
|
|
static void d3d9_hlsl_overlay_vertex_geom(
|
|
void *data,
|
|
unsigned index,
|
|
float x, float y,
|
|
float w, float h)
|
|
{
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
|
|
if (!d3d)
|
|
return;
|
|
|
|
y = 1.0f - y;
|
|
h = -h;
|
|
d3d->overlays[index].vert_coords[0] = x;
|
|
d3d->overlays[index].vert_coords[1] = y;
|
|
d3d->overlays[index].vert_coords[2] = w;
|
|
d3d->overlays[index].vert_coords[3] = h;
|
|
}
|
|
|
|
static bool d3d9_hlsl_overlay_load(void *data,
|
|
const void *image_data, unsigned num_images)
|
|
{
|
|
unsigned i, y;
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
const struct texture_image *images = (const struct texture_image*)
|
|
image_data;
|
|
|
|
if (!d3d)
|
|
return false;
|
|
|
|
d3d9_hlsl_free_overlays(d3d);
|
|
d3d->overlays = (overlay_t*)calloc(num_images, sizeof(*d3d->overlays));
|
|
d3d->overlays_size = num_images;
|
|
|
|
for (i = 0; i < num_images; i++)
|
|
{
|
|
D3DLOCKED_RECT d3dlr;
|
|
unsigned width = images[i].width;
|
|
unsigned height = images[i].height;
|
|
overlay_t *overlay = (overlay_t*)&d3d->overlays[i];
|
|
|
|
overlay->tex = NULL;
|
|
IDirect3DDevice9_CreateTexture(d3d->dev,
|
|
width, height, 1,
|
|
0,
|
|
D3D9_ARGB8888_FORMAT,
|
|
D3DPOOL_MANAGED,
|
|
(struct IDirect3DTexture9**)&overlay->tex, NULL);
|
|
|
|
if (!overlay->tex)
|
|
{
|
|
RARCH_ERR("[D3D9] Failed to create overlay texture.\n");
|
|
return false;
|
|
}
|
|
|
|
IDirect3DTexture9_LockRect((LPDIRECT3DTEXTURE9)overlay->tex, 0, &d3dlr, NULL, D3DLOCK_NOSYSLOCK);
|
|
{
|
|
uint32_t *dst = (uint32_t*)(d3dlr.pBits);
|
|
const uint32_t *src = images[i].pixels;
|
|
unsigned pitch = d3dlr.Pitch >> 2;
|
|
for (y = 0; y < height; y++, dst += pitch, src += width)
|
|
memcpy(dst, src, width << 2);
|
|
}
|
|
IDirect3DTexture9_UnlockRect((LPDIRECT3DTEXTURE9)overlay->tex, 0);
|
|
|
|
overlay->tex_w = width;
|
|
overlay->tex_h = height;
|
|
|
|
/* Default. Stretch to whole screen. */
|
|
d3d9_hlsl_overlay_tex_geom(d3d, i, 0, 0, 1, 1);
|
|
d3d9_hlsl_overlay_vertex_geom(d3d, i, 0, 0, 1, 1);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void d3d9_hlsl_overlay_enable(void *data, bool state)
|
|
{
|
|
unsigned i;
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
|
|
if (!d3d)
|
|
return;
|
|
|
|
for (i = 0; i < d3d->overlays_size; i++)
|
|
d3d->overlays_enabled = state;
|
|
|
|
#ifndef XBOX
|
|
win32_show_cursor(d3d, state);
|
|
#endif
|
|
}
|
|
|
|
static void d3d9_hlsl_overlay_full_screen(void *data, bool enable)
|
|
{
|
|
unsigned i;
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
|
|
for (i = 0; i < d3d->overlays_size; i++)
|
|
d3d->overlays[i].fullscreen = enable;
|
|
}
|
|
|
|
static void d3d9_hlsl_overlay_set_alpha(void *data, unsigned index, float mod)
|
|
{
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
if (d3d)
|
|
d3d->overlays[index].alpha_mod = mod;
|
|
}
|
|
|
|
static const video_overlay_interface_t d3d9_hlsl_overlay_interface = {
|
|
d3d9_hlsl_overlay_enable,
|
|
d3d9_hlsl_overlay_load,
|
|
d3d9_hlsl_overlay_tex_geom,
|
|
d3d9_hlsl_overlay_vertex_geom,
|
|
d3d9_hlsl_overlay_full_screen,
|
|
d3d9_hlsl_overlay_set_alpha,
|
|
};
|
|
|
|
static void d3d9_hlsl_get_overlay_interface(void *data,
|
|
const video_overlay_interface_t **iface)
|
|
{
|
|
*iface = &d3d9_hlsl_overlay_interface;
|
|
}
|
|
#endif
|
|
|
|
#if defined(HAVE_MENU) || defined(HAVE_OVERLAY)
|
|
static void d3d9_hlsl_overlay_render(d3d9_video_t *d3d,
|
|
unsigned width,
|
|
unsigned height,
|
|
overlay_t *overlay, bool force_linear)
|
|
{
|
|
D3DTEXTUREFILTERTYPE filter_type;
|
|
LPDIRECT3DDEVICE9 dev;
|
|
struct video_viewport vp;
|
|
void *verts;
|
|
unsigned i;
|
|
Vertex vert[4];
|
|
static const D3DVERTEXELEMENT9 vElems[4] = {
|
|
{0, offsetof(Vertex, x), D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,
|
|
D3DDECLUSAGE_POSITION, 0},
|
|
{0, offsetof(Vertex, u), D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT,
|
|
D3DDECLUSAGE_TEXCOORD, 0},
|
|
{0, offsetof(Vertex, color), D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT,
|
|
D3DDECLUSAGE_COLOR, 0},
|
|
D3DDECL_END()
|
|
};
|
|
|
|
if (!overlay || !overlay->tex)
|
|
return;
|
|
|
|
dev = d3d->dev;
|
|
|
|
if (!overlay->vert_buf)
|
|
{
|
|
overlay->vert_buf = NULL;
|
|
#ifdef _XBOX
|
|
if (!SUCCEEDED(IDirect3DDevice9_CreateVertexBuffer(
|
|
dev, sizeof(vert), D3DUSAGE_WRITEONLY,
|
|
0,
|
|
D3DPOOL_MANAGED,
|
|
(LPDIRECT3DVERTEXBUFFER9*)&overlay->vert_buf, NULL)))
|
|
overlay->vert_buf = NULL;
|
|
#else
|
|
if (!SUCCEEDED(IDirect3DDevice9_CreateVertexBuffer(
|
|
dev, sizeof(vert), D3DUSAGE_WRITEONLY,
|
|
0,
|
|
D3DPOOL_MANAGED,
|
|
(LPDIRECT3DVERTEXBUFFER9*)&overlay->vert_buf, NULL)))
|
|
overlay->vert_buf = NULL;
|
|
#endif
|
|
|
|
if (!overlay->vert_buf)
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
vert[i].z = 0.5f;
|
|
vert[i].color = (((uint32_t)(overlay->alpha_mod * 0xFF)) << 24) | 0xFFFFFF;
|
|
}
|
|
|
|
d3d9_hlsl_viewport_info(d3d, &vp);
|
|
|
|
vert[0].x = overlay->vert_coords[0];
|
|
vert[1].x = overlay->vert_coords[0] + overlay->vert_coords[2];
|
|
vert[2].x = overlay->vert_coords[0];
|
|
vert[3].x = overlay->vert_coords[0] + overlay->vert_coords[2];
|
|
vert[0].y = overlay->vert_coords[1];
|
|
vert[1].y = overlay->vert_coords[1];
|
|
vert[2].y = overlay->vert_coords[1] + overlay->vert_coords[3];
|
|
vert[3].y = overlay->vert_coords[1] + overlay->vert_coords[3];
|
|
|
|
vert[0].u = overlay->tex_coords[0];
|
|
vert[1].u = overlay->tex_coords[0] + overlay->tex_coords[2];
|
|
vert[2].u = overlay->tex_coords[0];
|
|
vert[3].u = overlay->tex_coords[0] + overlay->tex_coords[2];
|
|
vert[0].v = overlay->tex_coords[1];
|
|
vert[1].v = overlay->tex_coords[1];
|
|
vert[2].v = overlay->tex_coords[1] + overlay->tex_coords[3];
|
|
vert[3].v = overlay->tex_coords[1] + overlay->tex_coords[3];
|
|
|
|
IDirect3DVertexBuffer9_Lock((LPDIRECT3DVERTEXBUFFER9)overlay->vert_buf, 0, 0, &verts, 0);
|
|
memcpy(verts, vert, sizeof(vert));
|
|
IDirect3DVertexBuffer9_Unlock((LPDIRECT3DVERTEXBUFFER9)overlay->vert_buf);
|
|
|
|
IDirect3DDevice9_SetRenderState(d3d->dev, D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
|
|
IDirect3DDevice9_SetRenderState(d3d->dev, D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
|
|
IDirect3DDevice9_SetRenderState(d3d->dev, D3DRS_ALPHABLENDENABLE, true);
|
|
|
|
/* Use cached vertex declaration for overlay. */
|
|
if (!d3d9_hlsl_overlay_decl)
|
|
IDirect3DDevice9_CreateVertexDeclaration(dev,
|
|
(const D3DVERTEXELEMENT9*)&vElems, (IDirect3DVertexDeclaration9**)&d3d9_hlsl_overlay_decl);
|
|
IDirect3DDevice9_SetVertexDeclaration(dev, d3d9_hlsl_overlay_decl);
|
|
|
|
IDirect3DDevice9_SetStreamSource(dev, 0,
|
|
(LPDIRECT3DVERTEXBUFFER9)overlay->vert_buf, 0, sizeof(*vert));
|
|
|
|
if (overlay->fullscreen)
|
|
{
|
|
D3DVIEWPORT9 vp_full;
|
|
|
|
vp_full.X = 0;
|
|
vp_full.Y = 0;
|
|
vp_full.Width = width;
|
|
vp_full.Height = height;
|
|
vp_full.MinZ = 0.0f;
|
|
vp_full.MaxZ = 1.0f;
|
|
IDirect3DDevice9_SetViewport(dev, (D3DVIEWPORT9*)&vp_full);
|
|
}
|
|
|
|
filter_type = D3DTEXF_LINEAR;
|
|
|
|
if (!force_linear)
|
|
{
|
|
settings_t *settings = config_get_ptr();
|
|
bool menu_linear_filter = settings->bools.menu_linear_filter;
|
|
if (!menu_linear_filter)
|
|
filter_type = D3DTEXF_POINT;
|
|
}
|
|
|
|
/* Render overlay. */
|
|
IDirect3DDevice9_SetTexture(dev, 0,(IDirect3DBaseTexture9*)overlay->tex);
|
|
IDirect3DDevice9_SetSamplerState(dev,0,D3DSAMP_ADDRESSU,
|
|
D3DTADDRESS_BORDER);
|
|
IDirect3DDevice9_SetSamplerState(dev,0,D3DSAMP_ADDRESSV,
|
|
D3DTADDRESS_BORDER);
|
|
IDirect3DDevice9_SetSamplerState(dev,0,D3DSAMP_MINFILTER, filter_type);
|
|
IDirect3DDevice9_SetSamplerState(dev,0,D3DSAMP_MAGFILTER, filter_type);
|
|
IDirect3DDevice9_BeginScene(dev);
|
|
IDirect3DDevice9_DrawPrimitive(dev, D3DPT_TRIANGLESTRIP, 0, 2);
|
|
IDirect3DDevice9_EndScene(dev);
|
|
|
|
/* Restore previous state. */
|
|
IDirect3DDevice9_SetRenderState(dev, D3DRS_ALPHABLENDENABLE, false);
|
|
IDirect3DDevice9_SetViewport(dev, (D3DVIEWPORT9*)&d3d->out_vp);
|
|
}
|
|
|
|
#endif
|
|
|
|
static void d3d9_hlsl_free(void *data)
|
|
{
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
|
|
if (!d3d)
|
|
return;
|
|
|
|
#ifdef HAVE_OVERLAY
|
|
d3d9_hlsl_free_overlays(d3d);
|
|
#endif
|
|
|
|
if (d3d->menu)
|
|
{
|
|
if ((LPDIRECT3DTEXTURE9)d3d->menu->tex)
|
|
IDirect3DTexture9_Release((LPDIRECT3DTEXTURE9)d3d->menu->tex);
|
|
d3d9_vertex_buffer_free(d3d->menu->vert_buf, NULL);
|
|
free(d3d->menu);
|
|
}
|
|
d3d->menu = NULL;
|
|
|
|
d3d9_hlsl_deinitialize(d3d);
|
|
|
|
if (d3d9_hlsl_white_texture)
|
|
{
|
|
IDirect3DTexture9_Release(d3d9_hlsl_white_texture);
|
|
d3d9_hlsl_white_texture = NULL;
|
|
}
|
|
|
|
if (d3d->shader_path && *d3d->shader_path)
|
|
free(d3d->shader_path);
|
|
|
|
IDirect3DDevice9_Release(d3d->dev);
|
|
IDirect3D9_Release(d3d->d3d9);
|
|
d3d->shader_path = NULL;
|
|
d3d->dev = NULL;
|
|
d3d->d3d9 = NULL;
|
|
|
|
d3d9_deinitialize_symbols();
|
|
|
|
#ifndef _XBOX
|
|
win32_monitor_from_window();
|
|
win32_destroy_window();
|
|
#endif
|
|
free(d3d);
|
|
}
|
|
|
|
static bool d3d9_hlsl_frame(void *data, const void *frame,
|
|
unsigned frame_width, unsigned frame_height,
|
|
uint64_t frame_count, unsigned pitch,
|
|
const char *msg, video_frame_info_t *video_info)
|
|
{
|
|
D3DVIEWPORT9 screen_vp;
|
|
unsigned i = 0;
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
unsigned width = video_info->width;
|
|
unsigned height = video_info->height;
|
|
bool statistics_show = video_info->statistics_show;
|
|
unsigned black_frame_insertion = video_info->black_frame_insertion;
|
|
struct font_params *osd_params = (struct font_params*)
|
|
&video_info->osd_stat_params;
|
|
const char *stat_text = video_info->stat_text;
|
|
#ifdef HAVE_MENU
|
|
bool menu_is_alive = (video_info->menu_st_flags & MENU_ST_FLAG_ALIVE) ? true : false;
|
|
#else
|
|
bool menu_is_alive = false;
|
|
#endif
|
|
bool overlay_behind_menu = video_info->overlay_behind_menu;
|
|
#ifdef HAVE_GFX_WIDGETS
|
|
bool widgets_active = video_info->widgets_active;
|
|
#endif
|
|
|
|
if (!frame)
|
|
return true;
|
|
|
|
/* We cannot recover in fullscreen. */
|
|
if (d3d->needs_restore)
|
|
{
|
|
#ifndef _XBOX
|
|
HWND window = win32_get_window();
|
|
if (IsIconic(window))
|
|
return true;
|
|
#endif
|
|
|
|
if (!d3d9_hlsl_restore(d3d))
|
|
{
|
|
video_driver_state_t *video_st = video_state_get_ptr();
|
|
RARCH_ERR("[D3D9 HLSL] Failed to restore. Requesting reinit.\n");
|
|
video_st->flags |= VIDEO_FLAG_GPU_DEVICE_LOST;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (d3d->should_resize)
|
|
{
|
|
hlsl_renderchain_t *_chain = (hlsl_renderchain_t*)d3d->renderchain_data;
|
|
d3d9_hlsl_renderchain_t *chain = (d3d9_hlsl_renderchain_t*)&_chain->chain;
|
|
|
|
d3d9_hlsl_set_viewport(d3d, width, height, false, true);
|
|
|
|
if (chain)
|
|
chain->out_vp = (D3DVIEWPORT9*)&d3d->out_vp;
|
|
|
|
d3d9_recompute_pass_sizes(chain->dev, chain, d3d);
|
|
|
|
d3d->should_resize = false;
|
|
}
|
|
|
|
/* render_chain() only clears out viewport,
|
|
* clear out everything. */
|
|
screen_vp.X = 0;
|
|
screen_vp.Y = 0;
|
|
screen_vp.MinZ = 0;
|
|
screen_vp.MaxZ = 1;
|
|
screen_vp.Width = width;
|
|
screen_vp.Height = height;
|
|
IDirect3DDevice9_SetViewport(d3d->dev, (D3DVIEWPORT9*)&screen_vp);
|
|
IDirect3DDevice9_Clear(d3d->dev, 0, 0, D3DCLEAR_TARGET,
|
|
0, 1, 0);
|
|
|
|
IDirect3DDevice9_SetVertexShaderConstantF(d3d->dev, 0,
|
|
(const float*)&d3d->mvp, 4);
|
|
hlsl_d3d9_renderchain_render(
|
|
d3d, frame, frame_width, frame_height,
|
|
pitch, d3d->dev_rotation);
|
|
|
|
if (black_frame_insertion && !d3d->menu->enabled)
|
|
{
|
|
unsigned n;
|
|
for (n = 0; n < video_info->black_frame_insertion; ++n)
|
|
{
|
|
#ifdef _XBOX
|
|
bool ret = true;
|
|
IDirect3DDevice9_Present(d3d->dev, NULL, NULL, NULL, NULL);
|
|
#else
|
|
bool ret = (IDirect3DDevice9_Present(d3d->dev,
|
|
NULL, NULL, NULL, NULL) != D3DERR_DEVICELOST);
|
|
#endif
|
|
if (!ret || d3d->needs_restore)
|
|
return true;
|
|
IDirect3DDevice9_Clear(d3d->dev, 0, 0, D3DCLEAR_TARGET,
|
|
0, 1, 0);
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_OVERLAY
|
|
if (d3d->overlays_enabled && overlay_behind_menu)
|
|
{
|
|
IDirect3DDevice9_SetVertexShaderConstantF(d3d->dev, 0,
|
|
(const float*)&d3d->mvp_transposed, 4);
|
|
for (i = 0; i < d3d->overlays_size; i++)
|
|
d3d9_hlsl_overlay_render(d3d, width, height, &d3d->overlays[i], true);
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_MENU
|
|
if (d3d->menu && d3d->menu->enabled)
|
|
{
|
|
IDirect3DDevice9_SetVertexShaderConstantF(d3d->dev, 0,
|
|
(const float*)&d3d->mvp, 4);
|
|
d3d9_hlsl_overlay_render(d3d, width, height, d3d->menu, false);
|
|
|
|
d3d->menu_display.offset = 0;
|
|
IDirect3DDevice9_SetVertexDeclaration(d3d->dev, (LPDIRECT3DVERTEXDECLARATION9)d3d->menu_display.decl);
|
|
IDirect3DDevice9_SetStreamSource(d3d->dev, 0,
|
|
(LPDIRECT3DVERTEXBUFFER9)d3d->menu_display.buffer,
|
|
0, sizeof(Vertex));
|
|
IDirect3DDevice9_SetViewport(d3d->dev, (D3DVIEWPORT9*)&screen_vp);
|
|
IDirect3DDevice9_BeginScene(d3d->dev);
|
|
menu_driver_frame(menu_is_alive, video_info);
|
|
IDirect3DDevice9_EndScene(d3d->dev);
|
|
}
|
|
else if (statistics_show)
|
|
{
|
|
if (osd_params)
|
|
{
|
|
IDirect3DDevice9_SetViewport(d3d->dev, (D3DVIEWPORT9*)&screen_vp);
|
|
IDirect3DDevice9_BeginScene(d3d->dev);
|
|
font_driver_render_msg(d3d, stat_text, video_info->stat_text_len,
|
|
(const struct font_params*)osd_params, NULL);
|
|
IDirect3DDevice9_EndScene(d3d->dev);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_OVERLAY
|
|
if (d3d->overlays_enabled && !overlay_behind_menu)
|
|
{
|
|
IDirect3DDevice9_SetVertexShaderConstantF(d3d->dev, 0,
|
|
(const float*)&d3d->mvp_transposed, 4);
|
|
for (i = 0; i < d3d->overlays_size; i++)
|
|
d3d9_hlsl_overlay_render(d3d, width, height, &d3d->overlays[i], true);
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_GFX_WIDGETS
|
|
if (widgets_active)
|
|
{
|
|
hlsl_renderchain_t *_chain = (hlsl_renderchain_t*)d3d->renderchain_data;
|
|
RECT scissor_rect;
|
|
|
|
d3d->menu_display.offset = 0;
|
|
IDirect3DDevice9_SetVertexDeclaration(d3d->dev,
|
|
(LPDIRECT3DVERTEXDECLARATION9)d3d->menu_display.decl);
|
|
IDirect3DDevice9_SetStreamSource(d3d->dev, 0,
|
|
(LPDIRECT3DVERTEXBUFFER9)d3d->menu_display.buffer,
|
|
0, sizeof(Vertex));
|
|
IDirect3DDevice9_SetViewport(d3d->dev, (D3DVIEWPORT9*)&screen_vp);
|
|
|
|
/* Reset scissor rect to full screen */
|
|
scissor_rect.left = 0;
|
|
scissor_rect.top = 0;
|
|
scissor_rect.right = width;
|
|
scissor_rect.bottom = height;
|
|
IDirect3DDevice9_SetScissorRect(d3d->dev, &scissor_rect);
|
|
|
|
IDirect3DDevice9_SetRenderState(d3d->dev,
|
|
D3DRS_ALPHABLENDENABLE, true);
|
|
IDirect3DDevice9_SetRenderState(d3d->dev,
|
|
D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
|
|
IDirect3DDevice9_SetRenderState(d3d->dev,
|
|
D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
|
|
IDirect3DDevice9_SetRenderState(d3d->dev,
|
|
D3DRS_ZENABLE, false);
|
|
IDirect3DDevice9_SetRenderState(d3d->dev,
|
|
D3DRS_CULLMODE, D3DCULL_NONE);
|
|
if (_chain)
|
|
{
|
|
IDirect3DDevice9_SetVertexShader(d3d->dev, (LPDIRECT3DVERTEXSHADER9)(&_chain->stock_shader)->vprg);
|
|
IDirect3DDevice9_SetPixelShader(d3d->dev, (LPDIRECT3DPIXELSHADER9)(&_chain->stock_shader)->fprg);
|
|
}
|
|
IDirect3DDevice9_SetVertexShaderConstantF(d3d->dev, 0,
|
|
(const float*)&d3d->mvp, 4);
|
|
IDirect3DDevice9_BeginScene(d3d->dev);
|
|
gfx_widgets_frame(video_info);
|
|
IDirect3DDevice9_EndScene(d3d->dev);
|
|
}
|
|
#endif
|
|
|
|
if (msg && *msg)
|
|
{
|
|
IDirect3DDevice9_SetViewport(d3d->dev, (D3DVIEWPORT9*)&screen_vp);
|
|
IDirect3DDevice9_BeginScene(d3d->dev);
|
|
font_driver_render_msg(d3d, msg, strlen(msg), NULL, NULL);
|
|
IDirect3DDevice9_EndScene(d3d->dev);
|
|
}
|
|
|
|
video_driver_update_title(NULL);
|
|
|
|
#ifndef _XBOX
|
|
{
|
|
HRESULT hr = IDirect3DDevice9_Present(d3d->dev, NULL, NULL, NULL, NULL);
|
|
if (hr == D3DERR_DEVICELOST)
|
|
{
|
|
video_driver_state_t *video_st = video_state_get_ptr();
|
|
RARCH_WARN("[D3D9 HLSL] Device lost detected on Present().\n");
|
|
d3d->needs_restore = true;
|
|
video_st->flags |= VIDEO_FLAG_GPU_DEVICE_LOST;
|
|
return false;
|
|
}
|
|
}
|
|
#else
|
|
IDirect3DDevice9_Present(d3d->dev, NULL, NULL, NULL, NULL);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
static void d3d9_hlsl_set_aspect_ratio(void *data, unsigned aspect_ratio_idx)
|
|
{
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
|
|
if (!d3d)
|
|
return;
|
|
|
|
d3d->keep_aspect = true;
|
|
d3d->should_resize = true;
|
|
}
|
|
|
|
static void d3d9_hlsl_apply_state_changes(void *data)
|
|
{
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
if (d3d)
|
|
d3d->should_resize = true;
|
|
}
|
|
|
|
static void d3d9_hlsl_set_menu_texture_enable(void *data,
|
|
bool state, bool full_screen)
|
|
{
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
|
|
if (!d3d || !d3d->menu)
|
|
return;
|
|
|
|
d3d->menu->enabled = state;
|
|
d3d->menu->fullscreen = full_screen;
|
|
}
|
|
|
|
static void d3d9_hlsl_set_menu_texture_frame(void *data,
|
|
const void *frame, bool rgb32, unsigned width, unsigned height,
|
|
float alpha)
|
|
{
|
|
D3DLOCKED_RECT d3dlr;
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
|
|
if (!d3d || !d3d->menu)
|
|
return;
|
|
|
|
if ( (!d3d->menu->tex)
|
|
|| (d3d->menu->tex_w != width)
|
|
|| (d3d->menu->tex_h != height)
|
|
|| (d3d->menu_tex_rgb32 != rgb32))
|
|
{
|
|
if (d3d->menu->tex)
|
|
IDirect3DTexture9_Release((LPDIRECT3DTEXTURE9)d3d->menu->tex);
|
|
|
|
d3d->menu->tex = NULL;
|
|
/* RGUI sends 16bpp ARGB4444 (the d3d9 case in RGUI's pixel
|
|
* format dispatcher selects argb32_to_argb4444), so we can
|
|
* upload it byte-for-byte into a D3DFMT_A4R4G4B4 texture and
|
|
* skip the per-pixel CPU expansion to ARGB8888 the previous
|
|
* implementation did every frame. The rgb32 path is preserved
|
|
* for callers that hand us 32bpp data; in current practice no
|
|
* such caller exists, but the API contract supports it. */
|
|
IDirect3DDevice9_CreateTexture(d3d->dev,
|
|
width, height, 1,
|
|
0, rgb32 ? D3D9_ARGB8888_FORMAT : D3D9_ARGB4444_FORMAT,
|
|
D3DPOOL_MANAGED,
|
|
(struct IDirect3DTexture9**)&d3d->menu->tex, NULL);
|
|
|
|
if (!d3d->menu->tex)
|
|
{
|
|
RARCH_ERR("[D3D9] Failed to create menu texture.\n");
|
|
return;
|
|
}
|
|
|
|
d3d->menu->tex_w = width;
|
|
d3d->menu->tex_h = height;
|
|
d3d->menu_tex_rgb32 = rgb32;
|
|
}
|
|
|
|
d3d->menu->alpha_mod = alpha;
|
|
|
|
IDirect3DTexture9_LockRect((LPDIRECT3DTEXTURE9)d3d->menu->tex,
|
|
0, &d3dlr, NULL, D3DLOCK_NOSYSLOCK);
|
|
{
|
|
unsigned h;
|
|
|
|
if (rgb32)
|
|
{
|
|
uint8_t *dst = (uint8_t*)d3dlr.pBits;
|
|
const uint32_t *src = (const uint32_t*)frame;
|
|
|
|
for (h = 0; h < height; h++, dst += d3dlr.Pitch, src += width)
|
|
{
|
|
memcpy(dst, src, width * sizeof(uint32_t));
|
|
memset(dst + width * sizeof(uint32_t), 0,
|
|
d3dlr.Pitch - width * sizeof(uint32_t));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Direct ARGB4444 upload. The bit layout produced by
|
|
* argb32_to_argb4444 (host-endian uint16_t with A in bits
|
|
* 15..12, R 11..8, G 7..4, B 3..0) matches D3DFMT_A4R4G4B4
|
|
* exactly: D3D reads the locked memory as host-endian
|
|
* 16-bit units with the same bit assignments, so the same
|
|
* source bytes work on LE (PC) and BE (Xbox 360) hosts
|
|
* without a byte swap. */
|
|
uint8_t *dst = (uint8_t*)d3dlr.pBits;
|
|
const uint8_t *src = (const uint8_t*)frame;
|
|
unsigned src_pitch = width * sizeof(uint16_t);
|
|
unsigned row_bytes = width * sizeof(uint16_t);
|
|
|
|
for (h = 0; h < height; h++, dst += d3dlr.Pitch, src += src_pitch)
|
|
{
|
|
memcpy(dst, src, row_bytes);
|
|
if (d3dlr.Pitch > (int)row_bytes)
|
|
memset(dst + row_bytes, 0, d3dlr.Pitch - row_bytes);
|
|
}
|
|
}
|
|
}
|
|
|
|
IDirect3DTexture9_UnlockRect((LPDIRECT3DTEXTURE9)d3d->menu->tex, 0);
|
|
}
|
|
|
|
static void d3d9_hlsl_set_video_mode(void *data,
|
|
unsigned width, unsigned height,
|
|
bool fullscreen)
|
|
{
|
|
#ifndef _XBOX
|
|
/* TODO/FIXME - why is this called here? */
|
|
win32_show_cursor(data, !fullscreen);
|
|
#endif
|
|
}
|
|
|
|
struct d3d9_texture_info
|
|
{
|
|
void *userdata;
|
|
void *data;
|
|
enum texture_filter_type type;
|
|
};
|
|
|
|
static void d3d9_hlsl_video_texture_load_d3d(
|
|
struct d3d9_texture_info *info,
|
|
uintptr_t *id)
|
|
{
|
|
D3DLOCKED_RECT d3dlr;
|
|
LPDIRECT3DTEXTURE9 tex = NULL;
|
|
unsigned usage = 0;
|
|
bool want_mipmap = false;
|
|
d3d9_video_t *d3d = (d3d9_video_t*)info->userdata;
|
|
struct texture_image *ti = (struct texture_image*)info->data;
|
|
|
|
if (!ti)
|
|
return;
|
|
|
|
if ( (info->type == TEXTURE_FILTER_MIPMAP_LINEAR)
|
|
|| (info->type == TEXTURE_FILTER_MIPMAP_NEAREST))
|
|
want_mipmap = true;
|
|
|
|
#ifndef _XBOX
|
|
if (want_mipmap)
|
|
usage |= D3DUSAGE_AUTOGENMIPMAP;
|
|
#endif
|
|
tex = NULL;
|
|
if (!SUCCEEDED(IDirect3DDevice9_CreateTexture(d3d->dev,
|
|
ti->width, ti->height, 1,
|
|
usage, D3D9_ARGB8888_FORMAT,
|
|
D3DPOOL_MANAGED,
|
|
(struct IDirect3DTexture9**)&tex, NULL)))
|
|
return;
|
|
|
|
IDirect3DTexture9_LockRect(tex, 0, &d3dlr, NULL, D3DLOCK_NOSYSLOCK);
|
|
{
|
|
unsigned i;
|
|
uint32_t *dst = (uint32_t*)(d3dlr.pBits);
|
|
const uint32_t *src = ti->pixels;
|
|
unsigned pitch = d3dlr.Pitch >> 2;
|
|
for (i = 0; i < ti->height; i++, dst += pitch, src += ti->width)
|
|
memcpy(dst, src, ti->width << 2);
|
|
}
|
|
IDirect3DTexture9_UnlockRect(tex, 0);
|
|
|
|
*id = (uintptr_t)tex;
|
|
}
|
|
|
|
#ifdef HAVE_THREADS
|
|
static uintptr_t d3d9_hlsl_video_texture_load_wrap_d3d(void *data)
|
|
{
|
|
uintptr_t id = 0;
|
|
struct d3d9_texture_info *info = (struct d3d9_texture_info*)data;
|
|
if (!info)
|
|
return 0;
|
|
d3d9_hlsl_video_texture_load_d3d(info, &id);
|
|
return id;
|
|
}
|
|
#endif
|
|
|
|
uintptr_t d3d9_hlsl_load_texture(void *video_data, void *data,
|
|
bool threaded, enum texture_filter_type filter_type)
|
|
{
|
|
uintptr_t id = 0;
|
|
struct d3d9_texture_info info;
|
|
|
|
info.userdata = video_data;
|
|
info.data = data;
|
|
info.type = filter_type;
|
|
|
|
#ifdef HAVE_THREADS
|
|
if (threaded)
|
|
return video_thread_texture_handle(&info,
|
|
d3d9_hlsl_video_texture_load_wrap_d3d);
|
|
#endif
|
|
|
|
d3d9_hlsl_video_texture_load_d3d(&info, &id);
|
|
return id;
|
|
}
|
|
|
|
#ifdef HAVE_THREADS
|
|
static uintptr_t d3d9_hlsl_video_texture_unload_wrap_d3d(void *data)
|
|
{
|
|
uintptr_t id = (uintptr_t)data;
|
|
if (id)
|
|
{
|
|
LPDIRECT3DTEXTURE9 texid = (LPDIRECT3DTEXTURE9)id;
|
|
IDirect3DTexture9_Release(texid);
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static void d3d9_hlsl_unload_texture(void *data,
|
|
bool threaded, uintptr_t id)
|
|
{
|
|
LPDIRECT3DTEXTURE9 texid;
|
|
if (!id)
|
|
return;
|
|
|
|
#ifdef HAVE_THREADS
|
|
/* Dispatch Release to the video thread to serialise with
|
|
* in-flight draw calls that may still reference the
|
|
* texture. Matches the threading pattern already used by
|
|
* d3d9_hlsl_load_texture above. */
|
|
if (threaded)
|
|
{
|
|
video_thread_texture_handle((void*)id,
|
|
d3d9_hlsl_video_texture_unload_wrap_d3d);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
texid = (LPDIRECT3DTEXTURE9)id;
|
|
IDirect3DTexture9_Release(texid);
|
|
}
|
|
|
|
static struct video_shader *d3d9_hlsl_get_current_shader(void *data)
|
|
{
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
if (!d3d)
|
|
return NULL;
|
|
return &d3d->shader;
|
|
}
|
|
|
|
static const video_poke_interface_t d3d9_hlsl_poke_interface = {
|
|
d3d9_hlsl_get_flags,
|
|
d3d9_hlsl_load_texture,
|
|
d3d9_hlsl_unload_texture,
|
|
d3d9_hlsl_set_video_mode,
|
|
#if defined(_XBOX) || defined(__WINRT__)
|
|
NULL, /* get_refresh_rate */
|
|
#else
|
|
/* UWP does not expose this information easily */
|
|
NULL, /* refresh_rate - handled by display server */
|
|
#endif
|
|
NULL, /* set_filtering */
|
|
NULL, /* get_video_output_size */
|
|
NULL, /* get_video_output_prev */
|
|
NULL, /* get_video_output_next */
|
|
NULL, /* get_current_framebuffer */
|
|
NULL, /* get_proc_address */
|
|
d3d9_hlsl_set_aspect_ratio,
|
|
d3d9_hlsl_apply_state_changes,
|
|
d3d9_hlsl_set_menu_texture_frame,
|
|
d3d9_hlsl_set_menu_texture_enable,
|
|
d3d9_hlsl_set_osd_msg,
|
|
win32_show_cursor,
|
|
NULL, /* grab_mouse_toggle */
|
|
d3d9_hlsl_get_current_shader,
|
|
NULL, /* get_current_software_framebuffer */
|
|
NULL, /* get_hw_render_interface */
|
|
NULL, /* set_hdr_menu_nits */
|
|
NULL, /* set_hdr_paper_white_nits */
|
|
NULL, /* set_hdr_expand_gamut */
|
|
NULL, /* set_hdr_scanlines */
|
|
NULL, /* set_hdr_subpixel_layout */
|
|
d3d9_supports_texture_format,
|
|
d3d9_load_texture_compressed
|
|
};
|
|
|
|
static void d3d9_hlsl_get_poke_interface(void *data,
|
|
const video_poke_interface_t **iface)
|
|
{
|
|
*iface = &d3d9_hlsl_poke_interface;
|
|
}
|
|
|
|
#ifdef HAVE_GFX_WIDGETS
|
|
static bool d3d9_hlsl_gfx_widgets_enabled(void *data)
|
|
{
|
|
(void)data;
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
static void d3d9_hlsl_set_resize(d3d9_video_t *d3d,
|
|
unsigned new_width, unsigned new_height)
|
|
{
|
|
/* No changes? */
|
|
if ( (new_width == d3d->video_info.width)
|
|
&& (new_height == d3d->video_info.height))
|
|
return;
|
|
|
|
d3d->video_info.width = new_width;
|
|
d3d->video_info.height = new_height;
|
|
video_driver_set_output_size(new_width, new_height);
|
|
d3d->vp.full_width = new_width;
|
|
d3d->vp.full_height = new_height;
|
|
}
|
|
|
|
static bool d3d9_hlsl_alive(void *data)
|
|
{
|
|
unsigned temp_width = 0;
|
|
unsigned temp_height = 0;
|
|
bool ret = false;
|
|
bool quit = false;
|
|
bool resize = false;
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
|
|
/* Read from local bookkeeping rather than video_st (which would
|
|
* acquire context_lock + display_lock). d3d->vp.full_* is
|
|
* written at every set_size call site in this driver. */
|
|
temp_width = d3d->vp.full_width;
|
|
temp_height = d3d->vp.full_height;
|
|
|
|
win32_check_window(NULL, &quit, &resize, &temp_width, &temp_height);
|
|
|
|
if (quit)
|
|
d3d->quitting = quit;
|
|
|
|
if (resize)
|
|
{
|
|
d3d->should_resize = true;
|
|
d3d9_hlsl_set_resize(d3d, temp_width, temp_height);
|
|
d3d9_hlsl_restore(d3d);
|
|
}
|
|
|
|
ret = !quit;
|
|
|
|
if ( temp_width != 0 &&
|
|
temp_height != 0)
|
|
{
|
|
video_driver_set_output_size(temp_width, temp_height);
|
|
d3d->vp.full_width = temp_width;
|
|
d3d->vp.full_height = temp_height;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void d3d9_hlsl_set_nonblock_state(void *data, bool state,
|
|
bool adaptive_vsync_enabled,
|
|
unsigned swap_interval)
|
|
{
|
|
#ifdef _XBOX
|
|
int interval = 0;
|
|
#endif
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
|
|
if (!d3d)
|
|
return;
|
|
|
|
#ifdef _XBOX
|
|
if (!state)
|
|
interval = 1;
|
|
#endif
|
|
|
|
d3d->video_info.vsync = !state;
|
|
|
|
#ifdef _XBOX
|
|
IDirect3DDevice9_SetRenderState(d3d->dev,
|
|
D3DRS_PRESENTINTERVAL,
|
|
interval ?
|
|
D3DPRESENT_INTERVAL_ONE : D3DPRESENT_INTERVAL_IMMEDIATE);
|
|
#else
|
|
d3d->needs_restore = true;
|
|
d3d9_hlsl_restore(d3d);
|
|
#endif
|
|
}
|
|
|
|
#ifdef _XBOX
|
|
static bool d3d9_hlsl_suspend_screensaver(void *data, bool enable) { return true; }
|
|
#endif
|
|
|
|
static void d3d9_hlsl_set_rotation(void *data, unsigned rot)
|
|
{
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
if (d3d)
|
|
d3d->dev_rotation = rot;
|
|
}
|
|
|
|
static INLINE bool d3d9_hlsl_device_get_render_target_data(
|
|
LPDIRECT3DDEVICE9 dev,
|
|
LPDIRECT3DSURFACE9 src, LPDIRECT3DSURFACE9 dst)
|
|
{
|
|
#ifndef _XBOX
|
|
return ( dev
|
|
&& SUCCEEDED(IDirect3DDevice9_GetRenderTargetData(
|
|
dev, src, dst)));
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
static bool d3d9_hlsl_read_viewport(void *data, uint8_t *buffer, bool is_idle)
|
|
{
|
|
D3DLOCKED_RECT rect;
|
|
LPDIRECT3DSURFACE9 target = NULL;
|
|
LPDIRECT3DSURFACE9 dest = NULL;
|
|
bool ret = true;
|
|
d3d9_video_t *d3d = (d3d9_video_t*)data;
|
|
LPDIRECT3DDEVICE9 d3dr = d3d->dev;
|
|
unsigned width = d3d->vp.full_width;
|
|
unsigned height = d3d->vp.full_height;
|
|
|
|
if (
|
|
!(d3dr &&
|
|
SUCCEEDED(IDirect3DDevice9_GetRenderTarget(d3dr,
|
|
0, (LPDIRECT3DSURFACE9*)&target)))
|
|
|| !SUCCEEDED(IDirect3DDevice9_CreateOffscreenPlainSurface(d3dr,
|
|
width, height,
|
|
(D3DFORMAT)D3D9_XRGB8888_FORMAT, D3DPOOL_SYSTEMMEM,
|
|
(LPDIRECT3DSURFACE9*)&dest, NULL))
|
|
|| !d3d9_hlsl_device_get_render_target_data(d3dr, target, dest)
|
|
)
|
|
{
|
|
ret = false;
|
|
goto end;
|
|
}
|
|
|
|
IDirect3DSurface9_LockRect(dest, &rect, NULL, D3DLOCK_READONLY);
|
|
|
|
{
|
|
unsigned x, y;
|
|
unsigned vp_width = (d3d->out_vp.Width > width) ? width : d3d->out_vp.Width;
|
|
unsigned vp_height = (d3d->out_vp.Height > height) ? height : d3d->out_vp.Height;
|
|
unsigned pitchpix = rect.Pitch / 4;
|
|
const uint32_t *pixels = (const uint32_t*)rect.pBits;
|
|
|
|
pixels += d3d->out_vp.X;
|
|
pixels += (vp_height - 1) * pitchpix;
|
|
pixels -= d3d->out_vp.Y * pitchpix;
|
|
|
|
for (y = 0; y < vp_height; y++, pixels -= pitchpix)
|
|
{
|
|
for (x = 0; x < vp_width; x++)
|
|
{
|
|
uint32_t px = pixels[x];
|
|
*buffer++ = (px >> 0) & 0xff;
|
|
*buffer++ = (px >> 8) & 0xff;
|
|
*buffer++ = (px >> 16) & 0xff;
|
|
}
|
|
}
|
|
|
|
IDirect3DSurface9_UnlockRect(dest);
|
|
}
|
|
|
|
end:
|
|
if (target)
|
|
IDirect3DSurface9_Release(target);
|
|
if (dest)
|
|
IDirect3DSurface9_Release(dest);
|
|
return ret;
|
|
}
|
|
|
|
static bool d3d9_hlsl_has_windowed(void *data)
|
|
{
|
|
#ifdef _XBOX
|
|
return false;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
static font_renderer_t d3d9_hlsl_font = {
|
|
d3d9_font_init,
|
|
d3d9_font_free,
|
|
d3d9_font_render_msg,
|
|
"d3d9_hlsl",
|
|
d3d9_font_get_glyph,
|
|
NULL, /* bind_block */
|
|
NULL, /* flush */
|
|
d3d9_font_get_message_width,
|
|
d3d9_font_get_line_metrics
|
|
};
|
|
|
|
video_driver_t video_d3d9_hlsl = {
|
|
d3d9_hlsl_init,
|
|
d3d9_hlsl_frame,
|
|
d3d9_hlsl_set_nonblock_state,
|
|
d3d9_hlsl_alive,
|
|
NULL, /* focus */
|
|
#ifdef _XBOX
|
|
d3d9_hlsl_suspend_screensaver,
|
|
#else
|
|
win32_suspend_screensaver,
|
|
#endif
|
|
d3d9_hlsl_has_windowed,
|
|
d3d9_hlsl_set_shader,
|
|
d3d9_hlsl_free,
|
|
"d3d9_hlsl",
|
|
d3d9_hlsl_set_viewport,
|
|
d3d9_hlsl_set_rotation,
|
|
d3d9_hlsl_viewport_info,
|
|
d3d9_hlsl_read_viewport,
|
|
NULL, /* read_frame_raw */
|
|
#ifdef HAVE_OVERLAY
|
|
d3d9_hlsl_get_overlay_interface,
|
|
#endif
|
|
d3d9_hlsl_get_poke_interface,
|
|
NULL, /* wrap_type_to_enum */
|
|
NULL, /* shader_load_begin */
|
|
NULL, /* shader_load_step */
|
|
#ifdef HAVE_GFX_WIDGETS
|
|
d3d9_hlsl_gfx_widgets_enabled,
|
|
#endif
|
|
NULL, /* invalidate_hw_render_cache */
|
|
NULL, /* read_viewport_hdr */
|
|
&d3d9_hlsl_font
|
|
};
|
|
|
|
gfx_display_ctx_driver_t gfx_display_ctx_d3d9_hlsl = {
|
|
gfx_display_d3d9_hlsl_draw,
|
|
gfx_display_d3d9_hlsl_draw_pipeline,
|
|
gfx_display_d3d9_hlsl_blend_begin,
|
|
gfx_display_d3d9_hlsl_blend_end,
|
|
NULL, /* get_default_mvp */
|
|
NULL, /* get_default_vertices */
|
|
NULL, /* get_default_tex_coords */
|
|
&d3d9_hlsl_font,
|
|
GFX_VIDEO_DRIVER_DIRECT3D9_HLSL,
|
|
"d3d9_hlsl",
|
|
true,
|
|
gfx_display_d3d9_hlsl_scissor_begin,
|
|
gfx_display_d3d9_hlsl_scissor_end
|
|
};
|