951 lines
23 KiB
C
951 lines
23 KiB
C
|
/*
|
|||
|
* Copyright <EFBFBD> 2004 Keith Packard
|
|||
|
*
|
|||
|
* Permission to use, copy, modify, distribute, and sell this software and its
|
|||
|
* documentation for any purpose is hereby granted without fee, provided that
|
|||
|
* the above copyright notice appear in all copies and that both that
|
|||
|
* copyright notice and this permission notice appear in supporting
|
|||
|
* documentation, and that the name of Eric Anholt not be used in
|
|||
|
* advertising or publicity pertaining to distribution of the software without
|
|||
|
* specific, written prior permission. Eric Anholt makes no
|
|||
|
* representations about the suitability of this software for any purpose. It
|
|||
|
* is provided "as is" without express or implied warranty.
|
|||
|
*
|
|||
|
* ERIC ANHOLT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
|
|||
|
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
|
|||
|
* EVENT SHALL ERIC ANHOLT BE LIABLE FOR ANY SPECIAL, INDIRECT OR
|
|||
|
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
|
|||
|
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
|||
|
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|||
|
* PERFORMANCE OF THIS SOFTWARE.
|
|||
|
*
|
|||
|
* Based on mach64video.c by Keith Packard.
|
|||
|
*/
|
|||
|
/* $RCSId$ */
|
|||
|
|
|||
|
#ifdef HAVE_CONFIG_H
|
|||
|
#include <config.h>
|
|||
|
#endif
|
|||
|
#include "ati.h"
|
|||
|
#include "ati_dma.h"
|
|||
|
#include "ati_draw.h"
|
|||
|
#include "ati_reg.h"
|
|||
|
#include "kaa.h"
|
|||
|
|
|||
|
#include <X11/extensions/Xv.h>
|
|||
|
#include "fourcc.h"
|
|||
|
|
|||
|
#define MAKE_ATOM(a) MakeAtom(a, sizeof(a) - 1, TRUE)
|
|||
|
|
|||
|
static Atom xvBrightness, xvSaturation;
|
|||
|
|
|||
|
extern CARD8 ATIBltRop[16];
|
|||
|
|
|||
|
#define IMAGE_MAX_WIDTH 2048
|
|||
|
#define IMAGE_MAX_HEIGHT 2048
|
|||
|
|
|||
|
static void
|
|||
|
ATIStopVideo(KdScreenInfo *screen, pointer data, Bool exit)
|
|||
|
{
|
|||
|
ScreenPtr pScreen = screen->pScreen;
|
|||
|
ATIPortPrivPtr pPortPriv = (ATIPortPrivPtr)data;
|
|||
|
|
|||
|
REGION_EMPTY(screen->pScreen, &pPortPriv->clip);
|
|||
|
|
|||
|
if (pPortPriv->off_screen) {
|
|||
|
KdOffscreenFree (pScreen, pPortPriv->off_screen);
|
|||
|
pPortPriv->off_screen = 0;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
ATISetPortAttribute(KdScreenInfo *screen, Atom attribute, int value,
|
|||
|
pointer data)
|
|||
|
{
|
|||
|
return BadMatch;
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
ATIGetPortAttribute(KdScreenInfo *screen, Atom attribute, int *value,
|
|||
|
pointer data)
|
|||
|
{
|
|||
|
return BadMatch;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
ATIQueryBestSize(KdScreenInfo *screen, Bool motion, short vid_w, short vid_h,
|
|||
|
short drw_w, short drw_h, unsigned int *p_w, unsigned int *p_h,
|
|||
|
pointer data)
|
|||
|
{
|
|||
|
*p_w = drw_w;
|
|||
|
*p_h = drw_h;
|
|||
|
}
|
|||
|
|
|||
|
/* ATIClipVideo -
|
|||
|
|
|||
|
Takes the dst box in standard X BoxRec form (top and left
|
|||
|
edges inclusive, bottom and right exclusive). The new dst
|
|||
|
box is returned. The source boundaries are given (x1, y1
|
|||
|
inclusive, x2, y2 exclusive) and returned are the new source
|
|||
|
boundaries in 16.16 fixed point.
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
ATIClipVideo(BoxPtr dst, INT32 *x1, INT32 *x2, INT32 *y1, INT32 *y2,
|
|||
|
BoxPtr extents, INT32 width, INT32 height)
|
|||
|
{
|
|||
|
INT32 vscale, hscale, delta;
|
|||
|
int diff;
|
|||
|
|
|||
|
hscale = ((*x2 - *x1) << 16) / (dst->x2 - dst->x1);
|
|||
|
vscale = ((*y2 - *y1) << 16) / (dst->y2 - dst->y1);
|
|||
|
|
|||
|
*x1 <<= 16; *x2 <<= 16;
|
|||
|
*y1 <<= 16; *y2 <<= 16;
|
|||
|
|
|||
|
diff = extents->x1 - dst->x1;
|
|||
|
if (diff > 0) {
|
|||
|
dst->x1 = extents->x1;
|
|||
|
*x1 += diff * hscale;
|
|||
|
}
|
|||
|
diff = dst->x2 - extents->x2;
|
|||
|
if (diff > 0) {
|
|||
|
dst->x2 = extents->x2;
|
|||
|
*x2 -= diff * hscale;
|
|||
|
}
|
|||
|
diff = extents->y1 - dst->y1;
|
|||
|
if (diff > 0) {
|
|||
|
dst->y1 = extents->y1;
|
|||
|
*y1 += diff * vscale;
|
|||
|
}
|
|||
|
diff = dst->y2 - extents->y2;
|
|||
|
if (diff > 0) {
|
|||
|
dst->y2 = extents->y2;
|
|||
|
*y2 -= diff * vscale;
|
|||
|
}
|
|||
|
|
|||
|
if (*x1 < 0) {
|
|||
|
diff = (- *x1 + hscale - 1)/ hscale;
|
|||
|
dst->x1 += diff;
|
|||
|
*x1 += diff * hscale;
|
|||
|
}
|
|||
|
delta = *x2 - (width << 16);
|
|||
|
if (delta > 0) {
|
|||
|
diff = (delta + hscale - 1)/ hscale;
|
|||
|
dst->x2 -= diff;
|
|||
|
*x2 -= diff * hscale;
|
|||
|
}
|
|||
|
if (*y1 < 0) {
|
|||
|
diff = (- *y1 + vscale - 1)/ vscale;
|
|||
|
dst->y1 += diff;
|
|||
|
*y1 += diff * vscale;
|
|||
|
}
|
|||
|
delta = *y2 - (height << 16);
|
|||
|
if (delta > 0) {
|
|||
|
diff = (delta + vscale - 1)/ vscale;
|
|||
|
dst->y2 -= diff;
|
|||
|
*y2 -= diff * vscale;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
R128DisplayVideo(KdScreenInfo *screen, ATIPortPrivPtr pPortPriv)
|
|||
|
{
|
|||
|
ScreenPtr pScreen = screen->pScreen;
|
|||
|
KdScreenPriv(pScreen);
|
|||
|
ATIScreenInfo(pScreenPriv);
|
|||
|
CARD32 dstDatatype, srcDatatype;
|
|||
|
CARD32 dst_offset, dst_pitch;
|
|||
|
int dstxoff, dstyoff;
|
|||
|
PixmapPtr pPixmap = pPortPriv->pPixmap;
|
|||
|
int bpp = pPixmap->drawable.bitsPerPixel;
|
|||
|
RING_LOCALS;
|
|||
|
|
|||
|
BoxPtr pBox = REGION_RECTS(&pPortPriv->clip);
|
|||
|
int nBox = REGION_NUM_RECTS(&pPortPriv->clip);
|
|||
|
|
|||
|
if (pPortPriv->id == FOURCC_UYVY)
|
|||
|
srcDatatype = R128_DATATYPE_YVYU_422;
|
|||
|
else
|
|||
|
srcDatatype = R128_DATATYPE_VYUY_422;
|
|||
|
|
|||
|
switch (bpp)
|
|||
|
{
|
|||
|
case 16:
|
|||
|
if (pPixmap->drawable.depth == 15)
|
|||
|
dstDatatype = R128_DATATYPE_ARGB1555;
|
|||
|
else
|
|||
|
dstDatatype = R128_DATATYPE_RGB565;
|
|||
|
break;
|
|||
|
case 32:
|
|||
|
dstDatatype = R128_DATATYPE_ARGB8888;
|
|||
|
break;
|
|||
|
default:
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
dst_offset = ((CARD8 *)pPixmap->devPrivate.ptr -
|
|||
|
pScreenPriv->screen->memory_base);
|
|||
|
dst_pitch = pPixmap->devKind;
|
|||
|
#ifdef COMPOSITE
|
|||
|
dstxoff = -pPixmap->screen_x + pPixmap->drawable.x;
|
|||
|
dstyoff = -pPixmap->screen_y + pPixmap->drawable.y;
|
|||
|
#else
|
|||
|
dstxoff = 0;
|
|||
|
dstyoff = 0;
|
|||
|
#endif
|
|||
|
|
|||
|
BEGIN_DMA(18);
|
|||
|
OUT_REG(ATI_REG_DST_PITCH_OFFSET,
|
|||
|
((dst_pitch / bpp) << 21) | (dst_offset >> 5));
|
|||
|
OUT_REG(ATI_REG_DP_GUI_MASTER_CNTL,
|
|||
|
ATI_GMC_DST_PITCH_OFFSET_CNTL |
|
|||
|
ATI_GMC_BRUSH_NONE |
|
|||
|
(dstDatatype << 8) |
|
|||
|
ATI_GMC_SRC_DATATYPE_COLOR |
|
|||
|
(ATIBltRop[GXcopy] << 16) |
|
|||
|
R128_GMC_3D_FCN_EN |
|
|||
|
ATI_GMC_CLR_CMP_CNTL_DIS |
|
|||
|
R128_GMC_AUX_CLIP_DIS);
|
|||
|
OUT_REG(ATI_REG_DP_CNTL,
|
|||
|
ATI_DST_X_LEFT_TO_RIGHT | ATI_DST_Y_TOP_TO_BOTTOM );
|
|||
|
OUT_REG(R128_REG_SCALE_3D_CNTL,
|
|||
|
R128_SCALE_3D_SCALE |
|
|||
|
R128_SBLEND_ONE |
|
|||
|
R128_DBLEND_ZERO);
|
|||
|
OUT_REG(R128_REG_TEX_CNTL_C, R128_TEX_CACHE_FLUSH);
|
|||
|
OUT_REG(R128_REG_SCALE_3D_DATATYPE, srcDatatype);
|
|||
|
|
|||
|
OUT_RING(DMA_PACKET0(R128_REG_SCALE_PITCH, 5));
|
|||
|
OUT_RING(pPortPriv->src_pitch / 16);
|
|||
|
OUT_RING((pPortPriv->src_w << 16) / pPortPriv->dst_w);
|
|||
|
OUT_RING((pPortPriv->src_h << 16) / pPortPriv->dst_h);
|
|||
|
OUT_RING(0x0);
|
|||
|
OUT_RING(0x0);
|
|||
|
|
|||
|
END_DMA();
|
|||
|
|
|||
|
while (nBox--) {
|
|||
|
int srcX, srcY, dstX, dstY, srcw, srch, dstw, dsth;
|
|||
|
|
|||
|
dstX = pBox->x1 + dstxoff;
|
|||
|
dstY = pBox->y1 + dstyoff;
|
|||
|
dstw = pBox->x2 - pBox->x1;
|
|||
|
dsth = pBox->y2 - pBox->y1;
|
|||
|
srcX = (pBox->x1 - pPortPriv->dst_x1) *
|
|||
|
pPortPriv->src_w / pPortPriv->dst_w;
|
|||
|
srcY = (pBox->y1 - pPortPriv->dst_y1) *
|
|||
|
pPortPriv->src_h / pPortPriv->dst_h;
|
|||
|
srcw = pPortPriv->src_w - srcX;
|
|||
|
srch = pPortPriv->src_h - srcY;
|
|||
|
|
|||
|
BEGIN_DMA(6);
|
|||
|
/* R128_REG_SCALE_SRC_HEIGHT_WIDTH,
|
|||
|
* R128_REG_SCALE_OFFSET_0
|
|||
|
*/
|
|||
|
OUT_RING(DMA_PACKET0(R128_REG_SCALE_SRC_HEIGHT_WIDTH, 2));
|
|||
|
OUT_RING((srch << 16) | srcw);
|
|||
|
OUT_RING(pPortPriv->src_offset + srcY * pPortPriv->src_pitch +
|
|||
|
srcX * 2);
|
|||
|
/* R128_REG_SCALE_DST_X_Y
|
|||
|
* R128_REG_SCALE_DST_HEIGHT_WIDTH
|
|||
|
*/
|
|||
|
OUT_RING(DMA_PACKET0(R128_REG_SCALE_DST_X_Y, 2));
|
|||
|
OUT_RING((dstX << 16) | dstY);
|
|||
|
OUT_RING((dsth << 16) | dstw);
|
|||
|
END_DMA();
|
|||
|
pBox++;
|
|||
|
}
|
|||
|
#ifdef DAMAGEEXT
|
|||
|
/* XXX: Shouldn't this be in kxv.c instead? */
|
|||
|
DamageDamageRegion(pPortPriv->pDraw, &pPortPriv->clip);
|
|||
|
#endif
|
|||
|
KdMarkSync(pScreen);
|
|||
|
}
|
|||
|
|
|||
|
union intfloat {
|
|||
|
float f;
|
|||
|
CARD32 i;
|
|||
|
};
|
|||
|
|
|||
|
struct blend_vertex {
|
|||
|
union intfloat x, y;
|
|||
|
union intfloat s0, t0;
|
|||
|
};
|
|||
|
|
|||
|
#define VTX_DWORD_COUNT 4
|
|||
|
|
|||
|
#define VTX_OUT(vtx) \
|
|||
|
do { \
|
|||
|
OUT_RING(vtx.x.i); \
|
|||
|
OUT_RING(vtx.y.i); \
|
|||
|
OUT_RING(vtx.s0.i); \
|
|||
|
OUT_RING(vtx.t0.i); \
|
|||
|
} while (0)
|
|||
|
|
|||
|
static void
|
|||
|
RadeonDisplayVideo(KdScreenInfo *screen, ATIPortPrivPtr pPortPriv)
|
|||
|
{
|
|||
|
ScreenPtr pScreen = screen->pScreen;
|
|||
|
KdScreenPriv(pScreen);
|
|||
|
ATICardInfo(pScreenPriv);
|
|||
|
ATIScreenInfo(pScreenPriv);
|
|||
|
struct blend_vertex vtx[4];
|
|||
|
PixmapPtr pPixmap = pPortPriv->pPixmap;
|
|||
|
CARD32 txformat;
|
|||
|
CARD32 dst_offset, dst_pitch, dst_format;
|
|||
|
int dstxoff, dstyoff, pixel_shift;
|
|||
|
RING_LOCALS;
|
|||
|
|
|||
|
BoxPtr pBox = REGION_RECTS(&pPortPriv->clip);
|
|||
|
int nBox = REGION_NUM_RECTS(&pPortPriv->clip);
|
|||
|
|
|||
|
switch (pPixmap->drawable.bitsPerPixel)
|
|||
|
{
|
|||
|
case 16:
|
|||
|
if (pPixmap->drawable.depth == 15)
|
|||
|
dst_format = RADEON_COLOR_FORMAT_ARGB1555;
|
|||
|
else
|
|||
|
dst_format = RADEON_COLOR_FORMAT_RGB565;
|
|||
|
pixel_shift = 1;
|
|||
|
break;
|
|||
|
case 32:
|
|||
|
dst_format = RADEON_COLOR_FORMAT_ARGB8888;
|
|||
|
pixel_shift = 2;
|
|||
|
break;
|
|||
|
default:
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
dst_offset = ((CARD8 *)pPixmap->devPrivate.ptr -
|
|||
|
pScreenPriv->screen->memory_base);
|
|||
|
dst_pitch = pPixmap->devKind;
|
|||
|
|
|||
|
#ifdef COMPOSITE
|
|||
|
dstxoff = -pPixmap->screen_x + pPixmap->drawable.x;
|
|||
|
dstyoff = -pPixmap->screen_y + pPixmap->drawable.y;
|
|||
|
#else
|
|||
|
dstxoff = 0;
|
|||
|
dstyoff = 0;
|
|||
|
#endif
|
|||
|
|
|||
|
if (pPortPriv->id == FOURCC_UYVY)
|
|||
|
txformat = RADEON_TXFORMAT_YVYU422;
|
|||
|
else
|
|||
|
txformat = RADEON_TXFORMAT_VYUY422;
|
|||
|
|
|||
|
txformat |= RADEON_TXFORMAT_NON_POWER2;
|
|||
|
|
|||
|
/* RADEON_REG_PP_TXFILTER_0,
|
|||
|
* RADEON_REG_PP_TXFORMAT_0,
|
|||
|
* RADEON_REG_PP_TXOFFSET_0
|
|||
|
*/
|
|||
|
BEGIN_DMA(4);
|
|||
|
OUT_RING(DMA_PACKET0(RADEON_REG_PP_TXFILTER_0, 3));
|
|||
|
OUT_RING(RADEON_YUV_TO_RGB);
|
|||
|
OUT_RING(txformat);
|
|||
|
OUT_RING(pPortPriv->src_offset);
|
|||
|
END_DMA();
|
|||
|
|
|||
|
/* RADEON_REG_PP_TEX_SIZE_0,
|
|||
|
* RADEON_REG_PP_TEX_PITCH_0
|
|||
|
*/
|
|||
|
BEGIN_DMA(3);
|
|||
|
OUT_RING(DMA_PACKET0(RADEON_REG_PP_TEX_SIZE_0, 2));
|
|||
|
OUT_RING((pPixmap->drawable.width - 1) |
|
|||
|
((pPixmap->drawable.height - 1) << RADEON_TEX_VSIZE_SHIFT));
|
|||
|
OUT_RING(pPortPriv->src_pitch - 32);
|
|||
|
END_DMA();
|
|||
|
|
|||
|
BEGIN_DMA(14);
|
|||
|
OUT_REG(ATI_REG_WAIT_UNTIL,
|
|||
|
RADEON_WAIT_HOST_IDLECLEAN | RADEON_WAIT_2D_IDLECLEAN);
|
|||
|
|
|||
|
/* RADEON_REG_PP_CNTL,
|
|||
|
* RADEON_REG_RB3D_CNTL,
|
|||
|
* RADEON_REG_RB3D_COLOROFFSET
|
|||
|
*/
|
|||
|
OUT_RING(DMA_PACKET0(RADEON_REG_PP_CNTL, 3));
|
|||
|
OUT_RING(RADEON_TEX_0_ENABLE | RADEON_TEX_BLEND_0_ENABLE);
|
|||
|
OUT_RING(dst_format | RADEON_ALPHA_BLEND_ENABLE);
|
|||
|
OUT_RING(dst_offset);
|
|||
|
|
|||
|
OUT_REG(RADEON_REG_RB3D_COLORPITCH, dst_pitch >> pixel_shift);
|
|||
|
|
|||
|
OUT_REG(RADEON_REG_PP_TXCBLEND_0,
|
|||
|
RADEON_COLOR_ARG_A_ZERO |
|
|||
|
RADEON_COLOR_ARG_B_ZERO |
|
|||
|
RADEON_COLOR_ARG_C_T0_COLOR |
|
|||
|
RADEON_BLEND_CTL_ADD |
|
|||
|
RADEON_CLAMP_TX);
|
|||
|
OUT_REG(RADEON_REG_PP_TXABLEND_0,
|
|||
|
RADEON_ALPHA_ARG_A_ZERO |
|
|||
|
RADEON_ALPHA_ARG_B_ZERO |
|
|||
|
RADEON_ALPHA_ARG_C_T0_ALPHA |
|
|||
|
RADEON_BLEND_CTL_ADD |
|
|||
|
RADEON_CLAMP_TX);
|
|||
|
|
|||
|
OUT_REG(RADEON_REG_RB3D_BLENDCNTL,
|
|||
|
RADEON_SBLEND_GL_ONE | RADEON_DBLEND_GL_ZERO);
|
|||
|
|
|||
|
END_DMA();
|
|||
|
|
|||
|
while (nBox--) {
|
|||
|
float srcX, srcY, dstX, dstY, srcw, srch, dstw, dsth;
|
|||
|
|
|||
|
dstX = pBox->x1 + dstxoff;
|
|||
|
dstY = pBox->y1 + dstyoff;
|
|||
|
dstw = pBox->x2 - pBox->x1;
|
|||
|
dsth = pBox->y2 - pBox->y1;
|
|||
|
srcX = (pBox->x1 - pPortPriv->dst_x1) *
|
|||
|
pPortPriv->src_w / pPortPriv->dst_w;
|
|||
|
srcY = (pBox->y1 - pPortPriv->dst_y1) *
|
|||
|
pPortPriv->src_h / pPortPriv->dst_h;
|
|||
|
srcw = pPortPriv->src_w * (dstw / pPortPriv->dst_w);
|
|||
|
srch = pPortPriv->src_h * (dsth / pPortPriv->dst_h);
|
|||
|
|
|||
|
vtx[0].x.f = dstX;
|
|||
|
vtx[0].y.f = dstY;
|
|||
|
vtx[0].s0.f = srcX;
|
|||
|
vtx[0].t0.f = srcY;
|
|||
|
|
|||
|
vtx[1].x.f = dstX;
|
|||
|
vtx[1].y.f = dstY + dsth;
|
|||
|
vtx[1].s0.f = srcX;
|
|||
|
vtx[1].t0.f = srcY + srch;
|
|||
|
|
|||
|
vtx[2].x.f = dstX + dstw;
|
|||
|
vtx[2].y.f = dstY + dsth;
|
|||
|
vtx[2].s0.f = srcX + srcw;
|
|||
|
vtx[2].t0.f = srcY + srch;
|
|||
|
|
|||
|
vtx[3].x.f = dstX + dstw;
|
|||
|
vtx[3].y.f = dstY;
|
|||
|
vtx[3].s0.f = srcX + srcw;
|
|||
|
vtx[3].t0.f = srcY;
|
|||
|
|
|||
|
if (atic->is_r100) {
|
|||
|
BEGIN_DMA(4 * VTX_DWORD_COUNT + 3);
|
|||
|
OUT_RING(DMA_PACKET3(RADEON_CP_PACKET3_3D_DRAW_IMMD,
|
|||
|
4 * VTX_DWORD_COUNT + 2));
|
|||
|
OUT_RING(RADEON_CP_VC_FRMT_XY |
|
|||
|
RADEON_CP_VC_FRMT_ST0);
|
|||
|
OUT_RING(RADEON_CP_VC_CNTL_PRIM_TYPE_TRI_FAN |
|
|||
|
RADEON_CP_VC_CNTL_PRIM_WALK_RING |
|
|||
|
RADEON_CP_VC_CNTL_MAOS_ENABLE |
|
|||
|
RADEON_CP_VC_CNTL_VTX_FMT_RADEON_MODE |
|
|||
|
(4 << RADEON_CP_VC_CNTL_NUM_SHIFT));
|
|||
|
} else {
|
|||
|
BEGIN_DMA(4 * VTX_DWORD_COUNT + 2);
|
|||
|
OUT_RING(DMA_PACKET3(R200_CP_PACKET3_3D_DRAW_IMMD_2,
|
|||
|
4 * VTX_DWORD_COUNT + 1));
|
|||
|
OUT_RING(RADEON_CP_VC_CNTL_PRIM_TYPE_TRI_FAN |
|
|||
|
RADEON_CP_VC_CNTL_PRIM_WALK_RING |
|
|||
|
(4 << RADEON_CP_VC_CNTL_NUM_SHIFT));
|
|||
|
}
|
|||
|
|
|||
|
VTX_OUT(vtx[0]);
|
|||
|
VTX_OUT(vtx[1]);
|
|||
|
VTX_OUT(vtx[2]);
|
|||
|
VTX_OUT(vtx[3]);
|
|||
|
|
|||
|
END_DMA();
|
|||
|
|
|||
|
pBox++;
|
|||
|
}
|
|||
|
#ifdef DAMAGEEXT
|
|||
|
/* XXX: Shouldn't this be in kxv.c instead? */
|
|||
|
DamageDamageRegion(pPortPriv->pDraw, &pPortPriv->clip);
|
|||
|
#endif
|
|||
|
KdMarkSync(pScreen);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
ATIVideoSave(ScreenPtr pScreen, KdOffscreenArea *area)
|
|||
|
{
|
|||
|
KdScreenPriv(pScreen);
|
|||
|
ATIScreenInfo(pScreenPriv);
|
|||
|
ATIPortPrivPtr pPortPriv = atis->pAdaptor->pPortPrivates[0].ptr;
|
|||
|
|
|||
|
if (pPortPriv->off_screen == area)
|
|||
|
pPortPriv->off_screen = 0;
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
ATIPutImage(KdScreenInfo *screen, DrawablePtr pDraw,
|
|||
|
short src_x, short src_y,
|
|||
|
short drw_x, short drw_y,
|
|||
|
short src_w, short src_h,
|
|||
|
short drw_w, short drw_h,
|
|||
|
int id,
|
|||
|
unsigned char *buf,
|
|||
|
short width,
|
|||
|
short height,
|
|||
|
Bool sync,
|
|||
|
RegionPtr clipBoxes,
|
|||
|
pointer data)
|
|||
|
{
|
|||
|
ScreenPtr pScreen = screen->pScreen;
|
|||
|
KdScreenPriv(pScreen);
|
|||
|
ATICardInfo(pScreenPriv);
|
|||
|
ATIScreenInfo(pScreenPriv);
|
|||
|
ATIPortPrivPtr pPortPriv = (ATIPortPrivPtr)data;
|
|||
|
char *mmio = atic->reg_base;
|
|||
|
INT32 x1, x2, y1, y2;
|
|||
|
int randr = RR_Rotate_0 /* XXX */;
|
|||
|
int srcPitch, srcPitch2, dstPitch;
|
|||
|
int top, left, npixels, nlines, size;
|
|||
|
BoxRec dstBox;
|
|||
|
int dst_width = width, dst_height = height;
|
|||
|
int rot_x1, rot_y1, rot_x2, rot_y2;
|
|||
|
int dst_x1, dst_y1, dst_x2, dst_y2;
|
|||
|
int rot_src_w, rot_src_h, rot_drw_w, rot_drw_h;
|
|||
|
|
|||
|
/* Clip */
|
|||
|
x1 = src_x;
|
|||
|
x2 = src_x + src_w;
|
|||
|
y1 = src_y;
|
|||
|
y2 = src_y + src_h;
|
|||
|
|
|||
|
dstBox.x1 = drw_x;
|
|||
|
dstBox.x2 = drw_x + drw_w;
|
|||
|
dstBox.y1 = drw_y;
|
|||
|
dstBox.y2 = drw_y + drw_h;
|
|||
|
|
|||
|
ATIClipVideo(&dstBox, &x1, &x2, &y1, &y2,
|
|||
|
REGION_EXTENTS(pScreen, clipBoxes), width, height);
|
|||
|
|
|||
|
src_w = (x2 - x1) >> 16;
|
|||
|
src_h = (y2 - y1) >> 16;
|
|||
|
drw_w = dstBox.x2 - dstBox.x1;
|
|||
|
drw_h = dstBox.y2 - dstBox.y1;
|
|||
|
|
|||
|
if ((x1 >= x2) || (y1 >= y2))
|
|||
|
return Success;
|
|||
|
|
|||
|
if (mmio == NULL)
|
|||
|
return BadAlloc;
|
|||
|
|
|||
|
if (randr & (RR_Rotate_0|RR_Rotate_180)) {
|
|||
|
dst_width = width;
|
|||
|
dst_height = height;
|
|||
|
rot_src_w = src_w;
|
|||
|
rot_src_h = src_h;
|
|||
|
rot_drw_w = drw_w;
|
|||
|
rot_drw_h = drw_h;
|
|||
|
} else {
|
|||
|
dst_width = height;
|
|||
|
dst_height = width;
|
|||
|
rot_src_w = src_h;
|
|||
|
rot_src_h = src_w;
|
|||
|
rot_drw_w = drw_h;
|
|||
|
rot_drw_h = drw_w;
|
|||
|
}
|
|||
|
|
|||
|
switch (randr & RR_Rotate_All) {
|
|||
|
case RR_Rotate_0:
|
|||
|
default:
|
|||
|
dst_x1 = dstBox.x1;
|
|||
|
dst_y1 = dstBox.y1;
|
|||
|
dst_x2 = dstBox.x2;
|
|||
|
dst_y2 = dstBox.y2;
|
|||
|
rot_x1 = x1;
|
|||
|
rot_y1 = y1;
|
|||
|
rot_x2 = x2;
|
|||
|
rot_y2 = y2;
|
|||
|
break;
|
|||
|
case RR_Rotate_90:
|
|||
|
dst_x1 = dstBox.y1;
|
|||
|
dst_y1 = screen->height - dstBox.x2;
|
|||
|
dst_x2 = dstBox.y2;
|
|||
|
dst_y2 = screen->height - dstBox.x1;
|
|||
|
rot_x1 = y1;
|
|||
|
rot_y1 = (src_w << 16) - x2;
|
|||
|
rot_x2 = y2;
|
|||
|
rot_y2 = (src_w << 16) - x1;
|
|||
|
break;
|
|||
|
case RR_Rotate_180:
|
|||
|
dst_x1 = screen->width - dstBox.x2;
|
|||
|
dst_y1 = screen->height - dstBox.y2;
|
|||
|
dst_x2 = screen->width - dstBox.x1;
|
|||
|
dst_y2 = screen->height - dstBox.y1;
|
|||
|
rot_x1 = (src_w << 16) - x2;
|
|||
|
rot_y1 = (src_h << 16) - y2;
|
|||
|
rot_x2 = (src_w << 16) - x1;
|
|||
|
rot_y2 = (src_h << 16) - y1;
|
|||
|
break;
|
|||
|
case RR_Rotate_270:
|
|||
|
dst_x1 = screen->width - dstBox.y2;
|
|||
|
dst_y1 = dstBox.x1;
|
|||
|
dst_x2 = screen->width - dstBox.y1;
|
|||
|
dst_y2 = dstBox.x2;
|
|||
|
rot_x1 = (src_h << 16) - y2;
|
|||
|
rot_y1 = x1;
|
|||
|
rot_x2 = (src_h << 16) - y1;
|
|||
|
rot_y2 = x2;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
switch(id) {
|
|||
|
case FOURCC_YV12:
|
|||
|
case FOURCC_I420:
|
|||
|
dstPitch = ((dst_width << 1) + 15) & ~15;
|
|||
|
srcPitch = (width + 3) & ~3;
|
|||
|
srcPitch2 = ((width >> 1) + 3) & ~3;
|
|||
|
size = dstPitch * dst_height;
|
|||
|
break;
|
|||
|
case FOURCC_UYVY:
|
|||
|
case FOURCC_YUY2:
|
|||
|
default:
|
|||
|
dstPitch = ((dst_width << 1) + 15) & ~15;
|
|||
|
srcPitch = (width << 1);
|
|||
|
srcPitch2 = 0;
|
|||
|
size = dstPitch * dst_height;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
if (pPortPriv->off_screen != NULL && size != pPortPriv->size) {
|
|||
|
KdOffscreenFree(screen->pScreen, pPortPriv->off_screen);
|
|||
|
pPortPriv->off_screen = 0;
|
|||
|
}
|
|||
|
|
|||
|
if (pPortPriv->off_screen == NULL) {
|
|||
|
pPortPriv->off_screen = KdOffscreenAlloc(screen->pScreen,
|
|||
|
size * 2, 64, TRUE, ATIVideoSave, pPortPriv);
|
|||
|
if (pPortPriv->off_screen == NULL)
|
|||
|
return BadAlloc;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
if (pDraw->type == DRAWABLE_WINDOW)
|
|||
|
pPortPriv->pPixmap =
|
|||
|
(*pScreen->GetWindowPixmap)((WindowPtr)pDraw);
|
|||
|
else
|
|||
|
pPortPriv->pPixmap = (PixmapPtr)pDraw;
|
|||
|
|
|||
|
/* Migrate the pixmap to offscreen if necessary. */
|
|||
|
if (!kaaPixmapIsOffscreen(pPortPriv->pPixmap))
|
|||
|
kaaMoveInPixmap(pPortPriv->pPixmap);
|
|||
|
|
|||
|
if (!kaaPixmapIsOffscreen(pPortPriv->pPixmap)) {
|
|||
|
return BadAlloc;
|
|||
|
}
|
|||
|
|
|||
|
pPortPriv->src_offset = pPortPriv->off_screen->offset;
|
|||
|
pPortPriv->src_addr = (CARD8 *)(pScreenPriv->screen->memory_base +
|
|||
|
pPortPriv->src_offset);
|
|||
|
pPortPriv->src_pitch = dstPitch;
|
|||
|
pPortPriv->size = size;
|
|||
|
pPortPriv->pDraw = pDraw;
|
|||
|
|
|||
|
/* copy data */
|
|||
|
top = rot_y1 >> 16;
|
|||
|
left = (rot_x1 >> 16) & ~1;
|
|||
|
npixels = ((((rot_x2 + 0xffff) >> 16) + 1) & ~1) - left;
|
|||
|
|
|||
|
/* Since we're probably overwriting the area that might still be used
|
|||
|
* for the last PutImage request, wait for idle.
|
|||
|
*/
|
|||
|
ATIWaitIdle(atis);
|
|||
|
|
|||
|
switch(id) {
|
|||
|
case FOURCC_YV12:
|
|||
|
case FOURCC_I420:
|
|||
|
top &= ~1;
|
|||
|
nlines = ((((rot_y2 + 0xffff) >> 16) + 1) & ~1) - top;
|
|||
|
KdXVCopyPlanarData(screen, buf, pPortPriv->src_addr, randr,
|
|||
|
srcPitch, srcPitch2, dstPitch, rot_src_w, rot_src_h,
|
|||
|
height, top, left, nlines, npixels, id);
|
|||
|
break;
|
|||
|
case FOURCC_UYVY:
|
|||
|
case FOURCC_YUY2:
|
|||
|
default:
|
|||
|
nlines = ((rot_y2 + 0xffff) >> 16) - top;
|
|||
|
KdXVCopyPackedData(screen, buf, pPortPriv->src_addr, randr,
|
|||
|
srcPitch, dstPitch, rot_src_w, rot_src_h, top, left,
|
|||
|
nlines, npixels);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
/* update cliplist */
|
|||
|
if (!REGION_EQUAL(screen->pScreen, &pPortPriv->clip, clipBoxes)) {
|
|||
|
REGION_COPY(screen->pScreen, &pPortPriv->clip, clipBoxes);
|
|||
|
}
|
|||
|
|
|||
|
pPortPriv->id = id;
|
|||
|
pPortPriv->src_x1 = rot_x1;
|
|||
|
pPortPriv->src_y1 = rot_y1;
|
|||
|
pPortPriv->src_x2 = rot_x2;
|
|||
|
pPortPriv->src_y2 = rot_y2;
|
|||
|
pPortPriv->src_w = rot_src_w;
|
|||
|
pPortPriv->src_h = rot_src_h;
|
|||
|
pPortPriv->dst_x1 = dst_x1;
|
|||
|
pPortPriv->dst_y1 = dst_y1;
|
|||
|
pPortPriv->dst_x2 = dst_x2;
|
|||
|
pPortPriv->dst_y2 = dst_y2;
|
|||
|
pPortPriv->dst_w = rot_drw_w;
|
|||
|
pPortPriv->dst_h = rot_drw_h;
|
|||
|
|
|||
|
if (atic->is_radeon)
|
|||
|
RadeonDisplayVideo(screen, pPortPriv);
|
|||
|
else
|
|||
|
R128DisplayVideo(screen, pPortPriv);
|
|||
|
|
|||
|
return Success;
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
ATIReputImage(KdScreenInfo *screen, DrawablePtr pDraw, short drw_x, short drw_y,
|
|||
|
RegionPtr clipBoxes, pointer data)
|
|||
|
{
|
|||
|
ScreenPtr pScreen = screen->pScreen;
|
|||
|
KdScreenPriv(pScreen);
|
|||
|
ATICardInfo(pScreenPriv);
|
|||
|
ATIPortPrivPtr pPortPriv = (ATIPortPrivPtr)data;
|
|||
|
BoxPtr pOldExtents = REGION_EXTENTS(screen->pScreen, &pPortPriv->clip);
|
|||
|
BoxPtr pNewExtents = REGION_EXTENTS(screen->pScreen, clipBoxes);
|
|||
|
|
|||
|
if (pOldExtents->x1 != pNewExtents->x1 ||
|
|||
|
pOldExtents->x2 != pNewExtents->x2 ||
|
|||
|
pOldExtents->y1 != pNewExtents->y1 ||
|
|||
|
pOldExtents->y2 != pNewExtents->y2)
|
|||
|
return BadMatch;
|
|||
|
|
|||
|
if (pDraw->type == DRAWABLE_WINDOW)
|
|||
|
pPortPriv->pPixmap =
|
|||
|
(*pScreen->GetWindowPixmap)((WindowPtr)pDraw);
|
|||
|
else
|
|||
|
pPortPriv->pPixmap = (PixmapPtr)pDraw;
|
|||
|
|
|||
|
if (!kaaPixmapIsOffscreen(pPortPriv->pPixmap))
|
|||
|
kaaMoveInPixmap(pPortPriv->pPixmap);
|
|||
|
|
|||
|
if (!kaaPixmapIsOffscreen(pPortPriv->pPixmap)) {
|
|||
|
ErrorF("err\n");
|
|||
|
return BadAlloc;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* update cliplist */
|
|||
|
if (!REGION_EQUAL(screen->pScreen, &pPortPriv->clip, clipBoxes))
|
|||
|
REGION_COPY(screen->pScreen, &pPortPriv->clip, clipBoxes);
|
|||
|
|
|||
|
/* XXX: What do the drw_x and drw_y here mean for us? */
|
|||
|
|
|||
|
if (atic->is_radeon)
|
|||
|
RadeonDisplayVideo(screen, pPortPriv);
|
|||
|
else
|
|||
|
R128DisplayVideo(screen, pPortPriv);
|
|||
|
|
|||
|
return Success;
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
ATIQueryImageAttributes(KdScreenInfo *screen, int id, unsigned short *w,
|
|||
|
unsigned short *h, int *pitches, int *offsets)
|
|||
|
{
|
|||
|
int size, tmp;
|
|||
|
|
|||
|
if (*w > IMAGE_MAX_WIDTH)
|
|||
|
*w = IMAGE_MAX_WIDTH;
|
|||
|
if (*h > IMAGE_MAX_HEIGHT)
|
|||
|
*h = IMAGE_MAX_HEIGHT;
|
|||
|
|
|||
|
*w = (*w + 1) & ~1;
|
|||
|
if (offsets)
|
|||
|
offsets[0] = 0;
|
|||
|
|
|||
|
switch (id)
|
|||
|
{
|
|||
|
case FOURCC_YV12:
|
|||
|
case FOURCC_I420:
|
|||
|
*h = (*h + 1) & ~1;
|
|||
|
size = (*w + 3) & ~3;
|
|||
|
if (pitches)
|
|||
|
pitches[0] = size;
|
|||
|
size *= *h;
|
|||
|
if (offsets)
|
|||
|
offsets[1] = size;
|
|||
|
tmp = ((*w >> 1) + 3) & ~3;
|
|||
|
if (pitches)
|
|||
|
pitches[1] = pitches[2] = tmp;
|
|||
|
tmp *= (*h >> 1);
|
|||
|
size += tmp;
|
|||
|
if (offsets)
|
|||
|
offsets[2] = size;
|
|||
|
size += tmp;
|
|||
|
break;
|
|||
|
case FOURCC_UYVY:
|
|||
|
case FOURCC_YUY2:
|
|||
|
default:
|
|||
|
size = *w << 1;
|
|||
|
if (pitches)
|
|||
|
pitches[0] = size;
|
|||
|
size *= *h;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
return size;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* client libraries expect an encoding */
|
|||
|
static KdVideoEncodingRec DummyEncoding[1] =
|
|||
|
{
|
|||
|
{
|
|||
|
0,
|
|||
|
"XV_IMAGE",
|
|||
|
IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT,
|
|||
|
{1, 1}
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
#define NUM_FORMATS 3
|
|||
|
|
|||
|
static KdVideoFormatRec Formats[NUM_FORMATS] =
|
|||
|
{
|
|||
|
{15, TrueColor}, {16, TrueColor}, {24, TrueColor}
|
|||
|
};
|
|||
|
|
|||
|
#define NUM_ATTRIBUTES 0
|
|||
|
|
|||
|
static KdAttributeRec Attributes[NUM_ATTRIBUTES] =
|
|||
|
{
|
|||
|
};
|
|||
|
|
|||
|
#define NUM_IMAGES 4
|
|||
|
|
|||
|
static KdImageRec Images[NUM_IMAGES] =
|
|||
|
{
|
|||
|
XVIMAGE_YUY2,
|
|||
|
XVIMAGE_YV12,
|
|||
|
XVIMAGE_I420,
|
|||
|
XVIMAGE_UYVY
|
|||
|
};
|
|||
|
|
|||
|
static KdVideoAdaptorPtr
|
|||
|
ATISetupImageVideo(ScreenPtr pScreen)
|
|||
|
{
|
|||
|
KdScreenPriv(pScreen);
|
|||
|
ATIScreenInfo(pScreenPriv);
|
|||
|
KdVideoAdaptorPtr adapt;
|
|||
|
ATIPortPrivPtr pPortPriv;
|
|||
|
int i;
|
|||
|
|
|||
|
atis->num_texture_ports = 16;
|
|||
|
|
|||
|
adapt = xcalloc(1, sizeof(KdVideoAdaptorRec) + atis->num_texture_ports *
|
|||
|
(sizeof(ATIPortPrivRec) + sizeof(DevUnion)));
|
|||
|
if (adapt == NULL)
|
|||
|
return NULL;
|
|||
|
|
|||
|
adapt->type = XvWindowMask | XvInputMask | XvImageMask;
|
|||
|
adapt->flags = VIDEO_CLIP_TO_VIEWPORT;
|
|||
|
adapt->name = "ATI Texture Video";
|
|||
|
adapt->nEncodings = 1;
|
|||
|
adapt->pEncodings = DummyEncoding;
|
|||
|
adapt->nFormats = NUM_FORMATS;
|
|||
|
adapt->pFormats = Formats;
|
|||
|
adapt->nPorts = atis->num_texture_ports;
|
|||
|
adapt->pPortPrivates = (DevUnion*)(&adapt[1]);
|
|||
|
|
|||
|
pPortPriv =
|
|||
|
(ATIPortPrivPtr)(&adapt->pPortPrivates[atis->num_texture_ports]);
|
|||
|
|
|||
|
for (i = 0; i < atis->num_texture_ports; i++)
|
|||
|
adapt->pPortPrivates[i].ptr = &pPortPriv[i];
|
|||
|
|
|||
|
adapt->nAttributes = NUM_ATTRIBUTES;
|
|||
|
adapt->pAttributes = Attributes;
|
|||
|
adapt->pImages = Images;
|
|||
|
adapt->nImages = NUM_IMAGES;
|
|||
|
adapt->PutVideo = NULL;
|
|||
|
adapt->PutStill = NULL;
|
|||
|
adapt->GetVideo = NULL;
|
|||
|
adapt->GetStill = NULL;
|
|||
|
adapt->StopVideo = ATIStopVideo;
|
|||
|
adapt->SetPortAttribute = ATISetPortAttribute;
|
|||
|
adapt->GetPortAttribute = ATIGetPortAttribute;
|
|||
|
adapt->QueryBestSize = ATIQueryBestSize;
|
|||
|
adapt->PutImage = ATIPutImage;
|
|||
|
adapt->ReputImage = ATIReputImage;
|
|||
|
adapt->QueryImageAttributes = ATIQueryImageAttributes;
|
|||
|
|
|||
|
/* gotta uninit this someplace */
|
|||
|
REGION_INIT(pScreen, &pPortPriv->clip, NullBox, 0);
|
|||
|
|
|||
|
atis->pAdaptor = adapt;
|
|||
|
|
|||
|
xvBrightness = MAKE_ATOM("XV_BRIGHTNESS");
|
|||
|
xvSaturation = MAKE_ATOM("XV_SATURATION");
|
|||
|
|
|||
|
return adapt;
|
|||
|
}
|
|||
|
|
|||
|
Bool ATIInitVideo(ScreenPtr pScreen)
|
|||
|
{
|
|||
|
KdScreenPriv(pScreen);
|
|||
|
ATIScreenInfo(pScreenPriv);
|
|||
|
ATICardInfo(pScreenPriv);
|
|||
|
KdScreenInfo *screen = pScreenPriv->screen;
|
|||
|
KdVideoAdaptorPtr *adaptors, *newAdaptors = NULL;
|
|||
|
KdVideoAdaptorPtr newAdaptor = NULL;
|
|||
|
int num_adaptors;
|
|||
|
|
|||
|
atis->pAdaptor = NULL;
|
|||
|
|
|||
|
if (atic->reg_base == NULL)
|
|||
|
return FALSE;
|
|||
|
if (atic->is_r200 || atic->is_r300)
|
|||
|
return FALSE;
|
|||
|
|
|||
|
num_adaptors = KdXVListGenericAdaptors(screen, &adaptors);
|
|||
|
|
|||
|
newAdaptor = ATISetupImageVideo(pScreen);
|
|||
|
|
|||
|
if (newAdaptor) {
|
|||
|
if (!num_adaptors) {
|
|||
|
num_adaptors = 1;
|
|||
|
adaptors = &newAdaptor;
|
|||
|
} else {
|
|||
|
newAdaptors = xalloc((num_adaptors + 1) *
|
|||
|
sizeof(KdVideoAdaptorPtr *));
|
|||
|
if (newAdaptors) {
|
|||
|
memcpy(newAdaptors, adaptors, num_adaptors *
|
|||
|
sizeof(KdVideoAdaptorPtr));
|
|||
|
newAdaptors[num_adaptors] = newAdaptor;
|
|||
|
adaptors = newAdaptors;
|
|||
|
num_adaptors++;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (num_adaptors)
|
|||
|
KdXVScreenInit(pScreen, adaptors, num_adaptors);
|
|||
|
|
|||
|
if (newAdaptors)
|
|||
|
xfree(newAdaptors);
|
|||
|
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
ATIFiniVideo(ScreenPtr pScreen)
|
|||
|
{
|
|||
|
KdScreenPriv(pScreen);
|
|||
|
ATIScreenInfo(pScreenPriv);
|
|||
|
KdVideoAdaptorPtr adapt = atis->pAdaptor;
|
|||
|
ATIPortPrivPtr pPortPriv;
|
|||
|
int i;
|
|||
|
|
|||
|
if (!adapt)
|
|||
|
return;
|
|||
|
|
|||
|
for (i = 0; i < atis->num_texture_ports; i++) {
|
|||
|
pPortPriv = (ATIPortPrivPtr)(&adapt->pPortPrivates[i].ptr);
|
|||
|
REGION_UNINIT(pScreen, &pPortPriv->clip);
|
|||
|
}
|
|||
|
xfree(adapt);
|
|||
|
atis->pAdaptor = NULL;
|
|||
|
}
|