mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-12-11 05:01:09 +00:00
538 lines
15 KiB
C++
538 lines
15 KiB
C++
/*
|
|
** win32iface.h
|
|
**
|
|
**---------------------------------------------------------------------------
|
|
** Copyright 1998-2008 Randy Heit
|
|
** All rights reserved.
|
|
**
|
|
** Redistribution and use in source and binary forms, with or without
|
|
** modification, are permitted provided that the following conditions
|
|
** are met:
|
|
**
|
|
** 1. Redistributions of source code must retain the above copyright
|
|
** notice, this list of conditions and the following disclaimer.
|
|
** 2. Redistributions in binary form must reproduce the above copyright
|
|
** notice, this list of conditions and the following disclaimer in the
|
|
** documentation and/or other materials provided with the distribution.
|
|
** 3. The name of the author may not be used to endorse or promote products
|
|
** derived from this software without specific prior written permission.
|
|
**
|
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
**---------------------------------------------------------------------------
|
|
**
|
|
*/
|
|
|
|
#ifndef __WIN32IFACE_H
|
|
#define __WIN32IFACE_H
|
|
|
|
#ifndef DIRECTDRAW_VERSION
|
|
#define DIRECTDRAW_VERSION 0x0300
|
|
#endif
|
|
#ifndef DIRECT3D_VERSION
|
|
#define DIRECT3D_VERSION 0x0900
|
|
#endif
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
#include <ddraw.h>
|
|
#include <d3d9.h>
|
|
|
|
#include "hardware.h"
|
|
|
|
#define SAFE_RELEASE(x) { if (x != NULL) { x->Release(); x = NULL; } }
|
|
|
|
EXTERN_CVAR (Bool, vid_vsync)
|
|
|
|
extern HANDLE FPSLimitEvent;
|
|
|
|
class D3DTex;
|
|
class D3DPal;
|
|
struct FSoftwareRenderer;
|
|
|
|
class Win32Video : public IVideo
|
|
{
|
|
public:
|
|
Win32Video (int parm);
|
|
~Win32Video ();
|
|
|
|
bool InitD3D9();
|
|
void InitDDraw();
|
|
|
|
EDisplayType GetDisplayType () { return DISPLAY_Both; }
|
|
void SetWindowedScale (float scale);
|
|
|
|
DFrameBuffer *CreateFrameBuffer (int width, int height, bool fs, DFrameBuffer *old);
|
|
|
|
void StartModeIterator (int bits, bool fs);
|
|
bool NextMode (int *width, int *height, bool *letterbox);
|
|
|
|
bool GoFullscreen (bool yes);
|
|
void BlankForGDI ();
|
|
|
|
void DumpAdapters ();
|
|
|
|
private:
|
|
struct ModeInfo
|
|
{
|
|
ModeInfo (int inX, int inY, int inBits, int inRealY, int inDoubling)
|
|
: next (NULL),
|
|
width (inX),
|
|
height (inY),
|
|
bits (inBits),
|
|
realheight (inRealY),
|
|
doubling (inDoubling)
|
|
{}
|
|
|
|
ModeInfo *next;
|
|
int width, height, bits;
|
|
int realheight;
|
|
int doubling;
|
|
} *m_Modes;
|
|
|
|
ModeInfo *m_IteratorMode;
|
|
int m_IteratorBits;
|
|
bool m_IteratorFS;
|
|
bool m_IsFullscreen;
|
|
UINT m_Adapter;
|
|
|
|
void AddMode (int x, int y, int bits, int baseHeight, int doubling);
|
|
void FreeModes ();
|
|
|
|
static HRESULT WINAPI EnumDDModesCB (LPDDSURFACEDESC desc, void *modes);
|
|
void AddD3DModes (UINT adapter, D3DFORMAT format);
|
|
void AddLowResModes ();
|
|
void AddLetterboxModes ();
|
|
void ScaleModes (int doubling);
|
|
|
|
friend class DDrawFB;
|
|
friend class D3DFB;
|
|
};
|
|
|
|
class BaseWinFB : public DFrameBuffer
|
|
{
|
|
DECLARE_ABSTRACT_CLASS(BaseWinFB, DFrameBuffer)
|
|
public:
|
|
BaseWinFB (int width, int height) : DFrameBuffer (width, height), Windowed (true) {}
|
|
|
|
bool IsFullscreen () { return !Windowed; }
|
|
virtual void Blank () = 0;
|
|
virtual bool PaintToWindow () = 0;
|
|
virtual HRESULT GetHR () = 0;
|
|
virtual void ScaleCoordsFromWindow(SWORD &x, SWORD &y);
|
|
|
|
protected:
|
|
virtual bool CreateResources () = 0;
|
|
virtual void ReleaseResources () = 0;
|
|
virtual int GetTrueHeight() { return GetHeight(); }
|
|
|
|
bool Windowed;
|
|
|
|
friend class Win32Video;
|
|
|
|
BaseWinFB() {}
|
|
};
|
|
|
|
class DDrawFB : public BaseWinFB
|
|
{
|
|
DECLARE_CLASS(DDrawFB, BaseWinFB)
|
|
public:
|
|
DDrawFB (int width, int height, bool fullscreen);
|
|
~DDrawFB ();
|
|
|
|
bool IsValid ();
|
|
bool Lock (bool buffer);
|
|
void Unlock ();
|
|
void ForceBuffering (bool force);
|
|
void Update ();
|
|
PalEntry *GetPalette ();
|
|
void GetFlashedPalette (PalEntry pal[256]);
|
|
void UpdatePalette ();
|
|
bool SetGamma (float gamma);
|
|
bool SetFlash (PalEntry rgb, int amount);
|
|
void GetFlash (PalEntry &rgb, int &amount);
|
|
int GetPageCount ();
|
|
int QueryNewPalette ();
|
|
void PaletteChanged ();
|
|
void SetVSync (bool vsync);
|
|
void NewRefreshRate();
|
|
HRESULT GetHR ();
|
|
bool Is8BitMode();
|
|
virtual int GetTrueHeight() { return TrueHeight; }
|
|
|
|
void Blank ();
|
|
bool PaintToWindow ();
|
|
|
|
private:
|
|
enum LockSurfRes { NoGood, Good, GoodWasLost };
|
|
|
|
bool CreateResources ();
|
|
void ReleaseResources ();
|
|
bool CreateSurfacesAttached ();
|
|
bool CreateSurfacesComplex ();
|
|
bool CreateBlitterSource ();
|
|
LockSurfRes LockSurf (LPRECT lockrect, LPDIRECTDRAWSURFACE surf);
|
|
void RebuildColorTable ();
|
|
void MaybeCreatePalette ();
|
|
bool AddBackBuf (LPDIRECTDRAWSURFACE *surface, int num);
|
|
HRESULT AttemptRestore ();
|
|
|
|
HRESULT LastHR;
|
|
BYTE GammaTable[3][256];
|
|
PalEntry SourcePalette[256];
|
|
PALETTEENTRY PalEntries[256];
|
|
DWORD FlipFlags;
|
|
|
|
LPDIRECTDRAWPALETTE Palette;
|
|
LPDIRECTDRAWSURFACE PrimarySurf;
|
|
LPDIRECTDRAWSURFACE BackSurf;
|
|
LPDIRECTDRAWSURFACE BackSurf2;
|
|
LPDIRECTDRAWSURFACE BlitSurf;
|
|
LPDIRECTDRAWSURFACE LockingSurf;
|
|
LPDIRECTDRAWCLIPPER Clipper;
|
|
HPALETTE GDIPalette;
|
|
DWORD ClipSize;
|
|
PalEntry Flash;
|
|
int FlashAmount;
|
|
int BufferCount;
|
|
int BufferPitch;
|
|
int TrueHeight;
|
|
int PixelDoubling;
|
|
float Gamma;
|
|
|
|
bool NeedGammaUpdate;
|
|
bool NeedPalUpdate;
|
|
bool NeedResRecreate;
|
|
bool PaletteChangeExpected;
|
|
bool MustBuffer; // The screen is not 8-bit, or there is no backbuffer
|
|
bool BufferingNow; // Most recent Lock was buffered
|
|
bool WasBuffering; // Second most recent Lock was buffered
|
|
bool Write8bit;
|
|
bool UpdatePending; // On final unlock, call Update()
|
|
bool UseBlitter; // Use blitter to copy from sys mem to video mem
|
|
bool UsePfx;
|
|
|
|
DDrawFB() {}
|
|
};
|
|
|
|
class D3DFB : public BaseWinFB
|
|
{
|
|
DECLARE_CLASS(D3DFB, BaseWinFB)
|
|
public:
|
|
D3DFB (UINT adapter, int width, int height, bool fullscreen);
|
|
~D3DFB ();
|
|
|
|
bool IsValid ();
|
|
bool Lock (bool buffered);
|
|
void Unlock ();
|
|
void Update ();
|
|
void Flip ();
|
|
PalEntry *GetPalette ();
|
|
void GetFlashedPalette (PalEntry palette[256]);
|
|
void UpdatePalette ();
|
|
bool SetGamma (float gamma);
|
|
bool SetFlash (PalEntry rgb, int amount);
|
|
void GetFlash (PalEntry &rgb, int &amount);
|
|
int GetPageCount ();
|
|
bool IsFullscreen ();
|
|
void PaletteChanged ();
|
|
int QueryNewPalette ();
|
|
void Blank ();
|
|
bool PaintToWindow ();
|
|
void SetVSync (bool vsync);
|
|
void NewRefreshRate();
|
|
void GetScreenshotBuffer(const BYTE *&buffer, int &pitch, ESSType &color_type);
|
|
void ReleaseScreenshotBuffer();
|
|
void SetBlendingRect (int x1, int y1, int x2, int y2);
|
|
bool Begin2D (bool copy3d);
|
|
void DrawBlendingRect ();
|
|
FNativeTexture *CreateTexture (FTexture *gametex, bool wrapping);
|
|
FNativePalette *CreatePalette (FRemapTable *remap);
|
|
void DrawTextureParms (FTexture *img, DrawParms &parms);
|
|
void Clear (int left, int top, int right, int bottom, int palcolor, uint32 color);
|
|
void Dim (PalEntry color, float amount, int x1, int y1, int w, int h);
|
|
void FlatFill (int left, int top, int right, int bottom, FTexture *src, bool local_origin);
|
|
void DrawLine(int x0, int y0, int x1, int y1, int palColor, uint32 realcolor);
|
|
void DrawPixel(int x, int y, int palcolor, uint32 rgbcolor);
|
|
void FillSimplePoly(FTexture *tex, FVector2 *points, int npoints,
|
|
double originx, double originy, double scalex, double scaley,
|
|
DAngle rotation, FDynamicColormap *colormap, PalEntry flatcolor, int lightlevel, int bottomclip) override;
|
|
bool WipeStartScreen(int type);
|
|
void WipeEndScreen();
|
|
bool WipeDo(int ticks);
|
|
void WipeCleanup();
|
|
HRESULT GetHR ();
|
|
bool Is8BitMode() { return false; }
|
|
virtual int GetTrueHeight() { return TrueHeight; }
|
|
|
|
private:
|
|
friend class D3DTex;
|
|
friend class D3DPal;
|
|
|
|
struct PackedTexture;
|
|
struct Atlas;
|
|
|
|
struct FBVERTEX
|
|
{
|
|
FLOAT x, y, z, rhw;
|
|
D3DCOLOR color0, color1;
|
|
FLOAT tu, tv;
|
|
};
|
|
#define D3DFVF_FBVERTEX (D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_SPECULAR | D3DFVF_TEX1)
|
|
|
|
struct BufferedTris
|
|
{
|
|
union
|
|
{
|
|
struct
|
|
{
|
|
BYTE Flags;
|
|
BYTE ShaderNum:4;
|
|
BYTE BlendOp:4;
|
|
BYTE SrcBlend, DestBlend;
|
|
};
|
|
DWORD Group1;
|
|
};
|
|
BYTE Desat;
|
|
D3DPal *Palette;
|
|
IDirect3DTexture9 *Texture;
|
|
WORD NumVerts; // Number of _unique_ vertices used by this set.
|
|
WORD NumTris; // Number of triangles used by this set.
|
|
};
|
|
|
|
enum
|
|
{
|
|
PSCONST_Desaturation = 1,
|
|
PSCONST_PaletteMod = 2,
|
|
PSCONST_Weights = 6,
|
|
PSCONST_Gamma = 7,
|
|
};
|
|
enum
|
|
{
|
|
SHADER_NormalColor,
|
|
SHADER_NormalColorPal,
|
|
SHADER_NormalColorInv,
|
|
SHADER_NormalColorPalInv,
|
|
|
|
SHADER_RedToAlpha,
|
|
SHADER_RedToAlphaInv,
|
|
|
|
SHADER_VertexColor,
|
|
|
|
SHADER_SpecialColormap,
|
|
SHADER_SpecialColormapPal,
|
|
|
|
SHADER_InGameColormap,
|
|
SHADER_InGameColormapDesat,
|
|
SHADER_InGameColormapInv,
|
|
SHADER_InGameColormapInvDesat,
|
|
SHADER_InGameColormapPal,
|
|
SHADER_InGameColormapPalDesat,
|
|
SHADER_InGameColormapPalInv,
|
|
SHADER_InGameColormapPalInvDesat,
|
|
|
|
SHADER_BurnWipe,
|
|
SHADER_GammaCorrection,
|
|
|
|
NUM_SHADERS
|
|
};
|
|
static const char *const ShaderNames[NUM_SHADERS];
|
|
|
|
void SetInitialState();
|
|
bool CreateResources();
|
|
void ReleaseResources();
|
|
bool LoadShaders();
|
|
void CreateBlockSurfaces();
|
|
bool CreateFBTexture();
|
|
bool CreatePaletteTexture();
|
|
bool CreateGammaTexture();
|
|
bool CreateVertexes();
|
|
void DoOffByOneCheck();
|
|
void UploadPalette();
|
|
void UpdateGammaTexture(float igamma);
|
|
void FillPresentParameters (D3DPRESENT_PARAMETERS *pp, bool fullscreen, bool vsync);
|
|
void CalcFullscreenCoords (FBVERTEX verts[4], bool viewarea_only, bool can_double, D3DCOLOR color0, D3DCOLOR color1) const;
|
|
bool Reset();
|
|
IDirect3DTexture9 *GetCurrentScreen(D3DPOOL pool=D3DPOOL_SYSTEMMEM);
|
|
void ReleaseDefaultPoolItems();
|
|
void KillNativePals();
|
|
void KillNativeTexs();
|
|
PackedTexture *AllocPackedTexture(int width, int height, bool wrapping, D3DFORMAT format);
|
|
void DrawPackedTextures(int packnum);
|
|
void DrawLetterbox();
|
|
void Draw3DPart(bool copy3d);
|
|
bool SetStyle(D3DTex *tex, DrawParms &parms, D3DCOLOR &color0, D3DCOLOR &color1, BufferedTris &quad);
|
|
static D3DBLEND GetStyleAlpha(int type);
|
|
static void SetColorOverlay(DWORD color, float alpha, D3DCOLOR &color0, D3DCOLOR &color1);
|
|
void DoWindowedGamma();
|
|
void AddColorOnlyQuad(int left, int top, int width, int height, D3DCOLOR color);
|
|
void AddColorOnlyRect(int left, int top, int width, int height, D3DCOLOR color);
|
|
void CheckQuadBatch(int numtris=2, int numverts=4);
|
|
void BeginQuadBatch();
|
|
void EndQuadBatch();
|
|
void BeginLineBatch();
|
|
void EndLineBatch();
|
|
void EndBatch();
|
|
void CopyNextFrontBuffer();
|
|
|
|
D3DCAPS9 DeviceCaps;
|
|
|
|
// State
|
|
void EnableAlphaTest(BOOL enabled);
|
|
void SetAlphaBlend(D3DBLENDOP op, D3DBLEND srcblend=D3DBLEND(0), D3DBLEND destblend=D3DBLEND(0));
|
|
void SetConstant(int cnum, float r, float g, float b, float a);
|
|
void SetPixelShader(IDirect3DPixelShader9 *shader);
|
|
void SetTexture(int tnum, IDirect3DTexture9 *texture);
|
|
void SetPaletteTexture(IDirect3DTexture9 *texture, int count, D3DCOLOR border_color);
|
|
void SetPalTexBilinearConstants(Atlas *texture);
|
|
|
|
BOOL AlphaTestEnabled;
|
|
BOOL AlphaBlendEnabled;
|
|
D3DBLENDOP AlphaBlendOp;
|
|
D3DBLEND AlphaSrcBlend;
|
|
D3DBLEND AlphaDestBlend;
|
|
float Constant[3][4];
|
|
D3DCOLOR CurBorderColor;
|
|
IDirect3DPixelShader9 *CurPixelShader;
|
|
IDirect3DTexture9 *Texture[5];
|
|
|
|
PalEntry SourcePalette[256];
|
|
D3DCOLOR BorderColor;
|
|
D3DCOLOR FlashColor0, FlashColor1;
|
|
PalEntry FlashColor;
|
|
int FlashAmount;
|
|
int TrueHeight;
|
|
int PixelDoubling;
|
|
int SkipAt;
|
|
int LBOffsetI;
|
|
int RenderTextureToggle;
|
|
int CurrRenderTexture;
|
|
float LBOffset;
|
|
float Gamma;
|
|
bool UpdatePending;
|
|
bool NeedPalUpdate;
|
|
bool NeedGammaUpdate;
|
|
int FBWidth, FBHeight;
|
|
bool VSync;
|
|
RECT BlendingRect;
|
|
int In2D;
|
|
bool InScene;
|
|
bool SM14;
|
|
bool GatheringWipeScreen;
|
|
bool AALines;
|
|
BYTE BlockNum;
|
|
D3DPal *Palettes;
|
|
D3DTex *Textures;
|
|
Atlas *Atlases;
|
|
HRESULT LastHR;
|
|
|
|
UINT Adapter;
|
|
IDirect3DDevice9 *D3DDevice;
|
|
IDirect3DTexture9 *FBTexture;
|
|
IDirect3DTexture9 *TempRenderTexture, *RenderTexture[2];
|
|
IDirect3DTexture9 *PaletteTexture;
|
|
IDirect3DTexture9 *GammaTexture;
|
|
IDirect3DTexture9 *ScreenshotTexture;
|
|
IDirect3DSurface9 *ScreenshotSurface;
|
|
IDirect3DSurface9 *FrontCopySurface;
|
|
|
|
IDirect3DVertexBuffer9 *VertexBuffer;
|
|
FBVERTEX *VertexData;
|
|
IDirect3DIndexBuffer9 *IndexBuffer;
|
|
WORD *IndexData;
|
|
BufferedTris *QuadExtra;
|
|
int VertexPos;
|
|
int IndexPos;
|
|
int QuadBatchPos;
|
|
enum { BATCH_None, BATCH_Quads, BATCH_Lines } BatchType;
|
|
|
|
IDirect3DPixelShader9 *Shaders[NUM_SHADERS];
|
|
IDirect3DPixelShader9 *GammaShader;
|
|
|
|
IDirect3DSurface9 *BlockSurface[2];
|
|
IDirect3DSurface9 *OldRenderTarget;
|
|
IDirect3DTexture9 *InitialWipeScreen, *FinalWipeScreen;
|
|
|
|
D3DFB() {}
|
|
|
|
class Wiper
|
|
{
|
|
public:
|
|
virtual ~Wiper();
|
|
virtual bool Run(int ticks, D3DFB *fb) = 0;
|
|
|
|
void DrawScreen(D3DFB *fb, IDirect3DTexture9 *tex,
|
|
D3DBLENDOP blendop=D3DBLENDOP(0), D3DCOLOR color0=0, D3DCOLOR color1=0xFFFFFFF);
|
|
};
|
|
|
|
class Wiper_Melt; friend class Wiper_Melt;
|
|
class Wiper_Burn; friend class Wiper_Burn;
|
|
class Wiper_Crossfade; friend class Wiper_Crossfade;
|
|
|
|
Wiper *ScreenWipe;
|
|
};
|
|
|
|
// Flags for a buffered quad
|
|
enum
|
|
{
|
|
BQF_GamePalette = 1,
|
|
BQF_CustomPalette = 7,
|
|
BQF_Paletted = 7,
|
|
BQF_Bilinear = 8,
|
|
BQF_WrapUV = 16,
|
|
BQF_InvertSource = 32,
|
|
BQF_DisableAlphaTest= 64,
|
|
BQF_Desaturated = 128,
|
|
};
|
|
|
|
// Shaders for a buffered quad
|
|
enum
|
|
{
|
|
BQS_PalTex,
|
|
BQS_Plain,
|
|
BQS_RedToAlpha,
|
|
BQS_ColorOnly,
|
|
BQS_SpecialColormap,
|
|
BQS_InGameColormap,
|
|
};
|
|
|
|
#if 0
|
|
#define STARTLOG do { if (!dbg) dbg = fopen ("e:/vid.log", "w"); } while(0)
|
|
#define STOPLOG do { if (dbg) { fclose (dbg); dbg=NULL; } } while(0)
|
|
#define LOG(x) do { if (dbg) { fprintf (dbg, x); fflush (dbg); } } while(0)
|
|
#define LOG1(x,y) do { if (dbg) { fprintf (dbg, x, y); fflush (dbg); } } while(0)
|
|
#define LOG2(x,y,z) do { if (dbg) { fprintf (dbg, x, y, z); fflush (dbg); } } while(0)
|
|
#define LOG3(x,y,z,zz) do { if (dbg) { fprintf (dbg, x, y, z, zz); fflush (dbg); } } while(0)
|
|
#define LOG4(x,y,z,a,b) do { if (dbg) { fprintf (dbg, x, y, z, a, b); fflush (dbg); } } while(0)
|
|
#define LOG5(x,y,z,a,b,c) do { if (dbg) { fprintf (dbg, x, y, z, a, b, c); fflush (dbg); } } while(0)
|
|
extern FILE *dbg;
|
|
#define VID_FILE_DEBUG 1
|
|
#elif _DEBUG && 0
|
|
#define STARTLOG
|
|
#define STOPLOG
|
|
#define LOG(x) { OutputDebugString(x); }
|
|
#define LOG1(x,y) { char poo[1024]; mysnprintf(poo, countof(poo), x, y); OutputDebugString(poo); }
|
|
#define LOG2(x,y,z) { char poo[1024]; mysnprintf(poo, countof(poo), x, y, z); OutputDebugString(poo); }
|
|
#define LOG3(x,y,z,zz) { char poo[1024]; mysnprintf(poo, countof(poo), x, y, z, zz); OutputDebugString(poo); }
|
|
#define LOG4(x,y,z,a,b) { char poo[1024]; mysnprintf(poo, countof(poo), x, y, z, a, b); OutputDebugString(poo); }
|
|
#define LOG5(x,y,z,a,b,c) { char poo[1024]; mysnprintf(poo, countof(poo), x, y, z, a, b, c); OutputDebugString(poo); }
|
|
#else
|
|
#define STARTLOG
|
|
#define STOPLOG
|
|
#define LOG(x)
|
|
#define LOG1(x,y)
|
|
#define LOG2(x,y,z)
|
|
#define LOG3(x,y,z,zz)
|
|
#define LOG4(x,y,z,a,b)
|
|
#define LOG5(x,y,z,a,b,c)
|
|
#endif
|
|
|
|
#endif // __WIN32IFACE_H
|