audk/MdeModulePkg/Library/FrameBufferBltLib/FrameBufferBltLib.c

719 lines
25 KiB
C

/** @file
FrameBufferBltLib - Library to perform blt operations on a frame buffer.
Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include <Uefi/UefiBaseType.h>
#include <Protocol/GraphicsOutput.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/FrameBufferBltLib.h>
struct FRAME_BUFFER_CONFIGURE {
UINT32 PixelsPerScanLine;
UINT32 BytesPerPixel;
UINT32 Width;
UINT32 Height;
UINT8 *FrameBuffer;
EFI_GRAPHICS_PIXEL_FORMAT PixelFormat;
EFI_PIXEL_BITMASK PixelMasks;
INT8 PixelShl[4]; // R-G-B-Rsvd
INT8 PixelShr[4]; // R-G-B-Rsvd
UINT8 LineBuffer[0];
};
CONST EFI_PIXEL_BITMASK mRgbPixelMasks = {
0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000
};
CONST EFI_PIXEL_BITMASK mBgrPixelMasks = {
0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000
};
/**
Initialize the bit mask in frame buffer configure.
@param BitMask The bit mask of pixel.
@param BytesPerPixel Size in bytes of pixel.
@param PixelShl Left shift array.
@param PixelShr Right shift array.
**/
VOID
FrameBufferBltLibConfigurePixelFormat (
IN CONST EFI_PIXEL_BITMASK *BitMask,
OUT UINT32 *BytesPerPixel,
OUT INT8 *PixelShl,
OUT INT8 *PixelShr
)
{
UINT8 Index;
UINT32 *Masks;
UINT32 MergedMasks;
ASSERT (BytesPerPixel != NULL);
MergedMasks = 0;
Masks = (UINT32*) BitMask;
for (Index = 0; Index < 3; Index++) {
ASSERT ((MergedMasks & Masks[Index]) == 0);
PixelShl[Index] = (INT8) HighBitSet32 (Masks[Index]) - 23 + (Index * 8);
if (PixelShl[Index] < 0) {
PixelShr[Index] = -PixelShl[Index];
PixelShl[Index] = 0;
} else {
PixelShr[Index] = 0;
}
DEBUG ((DEBUG_INFO, "%d: shl:%d shr:%d mask:%x\n", Index,
PixelShl[Index], PixelShr[Index], Masks[Index]));
MergedMasks = (UINT32) (MergedMasks | Masks[Index]);
}
MergedMasks = (UINT32) (MergedMasks | Masks[3]);
ASSERT (MergedMasks != 0);
*BytesPerPixel = (UINT32) ((HighBitSet32 (MergedMasks) + 7) / 8);
DEBUG ((DEBUG_INFO, "Bytes per pixel: %d\n", *BytesPerPixel));
}
/**
Create the configuration for a video frame buffer.
The configuration is returned in the caller provided buffer.
@param[in] FrameBuffer Pointer to the start of the frame buffer.
@param[in] FrameBufferInfo Describes the frame buffer characteristics.
@param[in,out] Configure The created configuration information.
@param[in,out] ConfigureSize Size of the configuration information.
@retval RETURN_SUCCESS The configuration was successful created.
@retval RETURN_BUFFER_TOO_SMALL The Configure is to too small. The required
size is returned in ConfigureSize.
@retval RETURN_UNSUPPORTED The requested mode is not supported by
this implementaion.
**/
RETURN_STATUS
EFIAPI
FrameBufferBltConfigure (
IN VOID *FrameBuffer,
IN EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *FrameBufferInfo,
IN OUT FRAME_BUFFER_CONFIGURE *Configure,
IN OUT UINTN *ConfigureSize
)
{
CONST EFI_PIXEL_BITMASK *BitMask;
UINT32 BytesPerPixel;
INT8 PixelShl[4];
INT8 PixelShr[4];
if (ConfigureSize == NULL) {
return RETURN_INVALID_PARAMETER;
}
switch (FrameBufferInfo->PixelFormat) {
case PixelRedGreenBlueReserved8BitPerColor:
BitMask = &mRgbPixelMasks;
break;
case PixelBlueGreenRedReserved8BitPerColor:
BitMask = &mBgrPixelMasks;
break;
case PixelBitMask:
BitMask = &FrameBufferInfo->PixelInformation;
break;
case PixelBltOnly:
ASSERT (FrameBufferInfo->PixelFormat != PixelBltOnly);
return RETURN_UNSUPPORTED;
default:
ASSERT (FALSE);
return RETURN_INVALID_PARAMETER;
}
if (FrameBufferInfo->PixelsPerScanLine < FrameBufferInfo->HorizontalResolution) {
return RETURN_UNSUPPORTED;
}
FrameBufferBltLibConfigurePixelFormat (BitMask, &BytesPerPixel, PixelShl, PixelShr);
if (*ConfigureSize < sizeof (FRAME_BUFFER_CONFIGURE)
+ FrameBufferInfo->HorizontalResolution * BytesPerPixel) {
*ConfigureSize = sizeof (FRAME_BUFFER_CONFIGURE)
+ FrameBufferInfo->HorizontalResolution * BytesPerPixel;
return RETURN_BUFFER_TOO_SMALL;
}
if (Configure == NULL) {
return RETURN_INVALID_PARAMETER;
}
CopyMem (&Configure->PixelMasks, BitMask, sizeof (*BitMask));
CopyMem (Configure->PixelShl, PixelShl, sizeof (PixelShl));
CopyMem (Configure->PixelShr, PixelShr, sizeof (PixelShr));
Configure->BytesPerPixel = BytesPerPixel;
Configure->PixelFormat = FrameBufferInfo->PixelFormat;
Configure->FrameBuffer = (UINT8*) FrameBuffer;
Configure->Width = FrameBufferInfo->HorizontalResolution;
Configure->Height = FrameBufferInfo->VerticalResolution;
Configure->PixelsPerScanLine = FrameBufferInfo->PixelsPerScanLine;
return RETURN_SUCCESS;
}
/**
Performs a UEFI Graphics Output Protocol Blt Video Fill.
@param[in] Configure Pointer to a configuration which was successfully
created by FrameBufferBltConfigure ().
@param[in] Color Color to fill the region with.
@param[in] DestinationX X location to start fill operation.
@param[in] DestinationY Y location to start fill operation.
@param[in] Width Width (in pixels) to fill.
@param[in] Height Height to fill.
@retval RETURN_INVALID_PARAMETER Invalid parameter was passed in.
@retval RETURN_SUCCESS The video was filled successfully.
**/
EFI_STATUS
FrameBufferBltLibVideoFill (
IN FRAME_BUFFER_CONFIGURE *Configure,
IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Color,
IN UINTN DestinationX,
IN UINTN DestinationY,
IN UINTN Width,
IN UINTN Height
)
{
UINTN IndexX;
UINTN IndexY;
UINT8 *Destination;
UINT8 Uint8;
UINT32 Uint32;
UINT64 WideFill;
BOOLEAN UseWideFill;
BOOLEAN LineBufferReady;
UINTN Offset;
UINTN WidthInBytes;
UINTN SizeInBytes;
//
// BltBuffer to Video: Source is BltBuffer, destination is Video
//
if (DestinationY + Height > Configure->Height) {
DEBUG ((EFI_D_VERBOSE, "VideoFill: Past screen (Y)\n"));
return RETURN_INVALID_PARAMETER;
}
if (DestinationX + Width > Configure->Width) {
DEBUG ((EFI_D_VERBOSE, "VideoFill: Past screen (X)\n"));
return RETURN_INVALID_PARAMETER;
}
if (Width == 0 || Height == 0) {
DEBUG ((EFI_D_VERBOSE, "VideoFill: Width or Height is 0\n"));
return RETURN_INVALID_PARAMETER;
}
WidthInBytes = Width * Configure->BytesPerPixel;
Uint32 = *(UINT32*) Color;
WideFill =
(UINT32) (
(((Uint32 << Configure->PixelShl[0]) >> Configure->PixelShr[0]) &
Configure->PixelMasks.RedMask) |
(((Uint32 << Configure->PixelShl[1]) >> Configure->PixelShr[1]) &
Configure->PixelMasks.GreenMask) |
(((Uint32 << Configure->PixelShl[2]) >> Configure->PixelShr[2]) &
Configure->PixelMasks.BlueMask)
);
DEBUG ((EFI_D_VERBOSE, "VideoFill: color=0x%x, wide-fill=0x%x\n",
Uint32, WideFill));
//
// If the size of the pixel data evenly divides the sizeof
// WideFill, then a wide fill operation can be used
//
UseWideFill = TRUE;
if ((sizeof (WideFill) % Configure->BytesPerPixel) == 0) {
for (IndexX = Configure->BytesPerPixel; IndexX < sizeof (WideFill); IndexX++) {
((UINT8*) &WideFill)[IndexX] = ((UINT8*) &WideFill)[IndexX % Configure->BytesPerPixel];
}
} else {
//
// If all the bytes in the pixel are the same value, then use
// a wide fill operation.
//
for (
IndexX = 1, Uint8 = ((UINT8*) &WideFill)[0];
IndexX < Configure->BytesPerPixel;
IndexX++) {
if (Uint8 != ((UINT8*) &WideFill)[IndexX]) {
UseWideFill = FALSE;
break;
}
}
if (UseWideFill) {
SetMem (&WideFill, sizeof (WideFill), Uint8);
}
}
if (UseWideFill && (DestinationX == 0) && (Width == Configure->PixelsPerScanLine)) {
DEBUG ((EFI_D_VERBOSE, "VideoFill (wide, one-shot)\n"));
Offset = DestinationY * Configure->PixelsPerScanLine;
Offset = Configure->BytesPerPixel * Offset;
Destination = Configure->FrameBuffer + Offset;
SizeInBytes = WidthInBytes * Height;
if (SizeInBytes >= 8) {
SetMem32 (Destination, SizeInBytes & ~3, (UINT32) WideFill);
Destination += SizeInBytes & ~3;
SizeInBytes &= 3;
}
if (SizeInBytes > 0) {
SetMem (Destination, SizeInBytes, (UINT8) (UINTN) WideFill);
}
} else {
LineBufferReady = FALSE;
for (IndexY = DestinationY; IndexY < (Height + DestinationY); IndexY++) {
Offset = (IndexY * Configure->PixelsPerScanLine) + DestinationX;
Offset = Configure->BytesPerPixel * Offset;
Destination = Configure->FrameBuffer + Offset;
if (UseWideFill && (((UINTN) Destination & 7) == 0)) {
DEBUG ((EFI_D_VERBOSE, "VideoFill (wide)\n"));
SizeInBytes = WidthInBytes;
if (SizeInBytes >= 8) {
SetMem64 (Destination, SizeInBytes & ~7, WideFill);
Destination += SizeInBytes & ~7;
SizeInBytes &= 7;
}
if (SizeInBytes > 0) {
CopyMem (Destination, &WideFill, SizeInBytes);
}
} else {
DEBUG ((EFI_D_VERBOSE, "VideoFill (not wide)\n"));
if (!LineBufferReady) {
CopyMem (Configure->LineBuffer, &WideFill, Configure->BytesPerPixel);
for (IndexX = 1; IndexX < Width; ) {
CopyMem (
(Configure->LineBuffer + (IndexX * Configure->BytesPerPixel)),
Configure->LineBuffer,
MIN (IndexX, Width - IndexX) * Configure->BytesPerPixel
);
IndexX += MIN (IndexX, Width - IndexX);
}
LineBufferReady = TRUE;
}
CopyMem (Destination, Configure->LineBuffer, WidthInBytes);
}
}
}
return RETURN_SUCCESS;
}
/**
Performs a UEFI Graphics Output Protocol Blt Video to Buffer operation
with extended parameters.
@param[in] Configure Pointer to a configuration which was successfully
created by FrameBufferBltConfigure ().
@param[out] BltBuffer Output buffer for pixel color data.
@param[in] SourceX X location within video.
@param[in] SourceY Y location within video.
@param[in] DestinationX X location within BltBuffer.
@param[in] DestinationY Y location within BltBuffer.
@param[in] Width Width (in pixels).
@param[in] Height Height.
@param[in] Delta Number of bytes in a row of BltBuffer.
@retval RETURN_INVALID_PARAMETER Invalid parameter were passed in.
@retval RETURN_SUCCESS The Blt operation was performed successfully.
**/
RETURN_STATUS
FrameBufferBltLibVideoToBltBuffer (
IN FRAME_BUFFER_CONFIGURE *Configure,
OUT EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer,
IN UINTN SourceX,
IN UINTN SourceY,
IN UINTN DestinationX,
IN UINTN DestinationY,
IN UINTN Width,
IN UINTN Height,
IN UINTN Delta
)
{
UINTN DstY;
UINTN SrcY;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Blt;
UINT8 *Source;
UINT8 *Destination;
UINTN IndexX;
UINT32 Uint32;
UINTN Offset;
UINTN WidthInBytes;
//
// Video to BltBuffer: Source is Video, destination is BltBuffer
//
if (SourceY + Height > Configure->Height) {
return RETURN_INVALID_PARAMETER;
}
if (SourceX + Width > Configure->Width) {
return RETURN_INVALID_PARAMETER;
}
if (Width == 0 || Height == 0) {
return RETURN_INVALID_PARAMETER;
}
//
// If Delta is zero, then the entire BltBuffer is being used, so Delta is
// the number of bytes in each row of BltBuffer. Since BltBuffer is Width
// pixels size, the number of bytes in each row can be computed.
//
if (Delta == 0) {
Delta = Width * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL);
}
WidthInBytes = Width * Configure->BytesPerPixel;
//
// Video to BltBuffer: Source is Video, destination is BltBuffer
//
for (SrcY = SourceY, DstY = DestinationY;
DstY < (Height + DestinationY);
SrcY++, DstY++) {
Offset = (SrcY * Configure->PixelsPerScanLine) + SourceX;
Offset = Configure->BytesPerPixel * Offset;
Source = Configure->FrameBuffer + Offset;
if (Configure->PixelFormat == PixelBlueGreenRedReserved8BitPerColor) {
Destination = (UINT8 *) BltBuffer + (DstY * Delta) + (DestinationX * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL));
} else {
Destination = Configure->LineBuffer;
}
CopyMem (Destination, Source, WidthInBytes);
if (Configure->PixelFormat != PixelBlueGreenRedReserved8BitPerColor) {
for (IndexX = 0; IndexX < Width; IndexX++) {
Blt = (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *)
((UINT8 *) BltBuffer + (DstY * Delta) +
(DestinationX + IndexX) * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL));
Uint32 = *(UINT32*) (Configure->LineBuffer + (IndexX * Configure->BytesPerPixel));
*(UINT32*) Blt =
(UINT32) (
(((Uint32 & Configure->PixelMasks.RedMask) >>
Configure->PixelShl[0]) << Configure->PixelShr[0]) |
(((Uint32 & Configure->PixelMasks.GreenMask) >>
Configure->PixelShl[1]) << Configure->PixelShr[1]) |
(((Uint32 & Configure->PixelMasks.BlueMask) >>
Configure->PixelShl[2]) << Configure->PixelShr[2])
);
}
}
}
return RETURN_SUCCESS;
}
/**
Performs a UEFI Graphics Output Protocol Blt Buffer to Video operation
with extended parameters.
@param[in] Configure Pointer to a configuration which was successfully
created by FrameBufferBltConfigure ().
@param[in] BltBuffer Output buffer for pixel color data.
@param[in] SourceX X location within BltBuffer.
@param[in] SourceY Y location within BltBuffer.
@param[in] DestinationX X location within video.
@param[in] DestinationY Y location within video.
@param[in] Width Width (in pixels).
@param[in] Height Height.
@param[in] Delta Number of bytes in a row of BltBuffer.
@retval RETURN_INVALID_PARAMETER Invalid parameter were passed in.
@retval RETURN_SUCCESS The Blt operation was performed successfully.
**/
RETURN_STATUS
FrameBufferBltLibBufferToVideo (
IN FRAME_BUFFER_CONFIGURE *Configure,
IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer,
IN UINTN SourceX,
IN UINTN SourceY,
IN UINTN DestinationX,
IN UINTN DestinationY,
IN UINTN Width,
IN UINTN Height,
IN UINTN Delta
)
{
UINTN DstY;
UINTN SrcY;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Blt;
UINT8 *Source;
UINT8 *Destination;
UINTN IndexX;
UINT32 Uint32;
UINTN Offset;
UINTN WidthInBytes;
//
// BltBuffer to Video: Source is BltBuffer, destination is Video
//
if (DestinationY + Height > Configure->Height) {
return RETURN_INVALID_PARAMETER;
}
if (DestinationX + Width > Configure->Width) {
return RETURN_INVALID_PARAMETER;
}
if (Width == 0 || Height == 0) {
return RETURN_INVALID_PARAMETER;
}
//
// If Delta is zero, then the entire BltBuffer is being used, so Delta is
// the number of bytes in each row of BltBuffer. Since BltBuffer is Width
// pixels size, the number of bytes in each row can be computed.
//
if (Delta == 0) {
Delta = Width * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL);
}
WidthInBytes = Width * Configure->BytesPerPixel;
for (SrcY = SourceY, DstY = DestinationY;
SrcY < (Height + SourceY);
SrcY++, DstY++) {
Offset = (DstY * Configure->PixelsPerScanLine) + DestinationX;
Offset = Configure->BytesPerPixel * Offset;
Destination = Configure->FrameBuffer + Offset;
if (Configure->PixelFormat == PixelBlueGreenRedReserved8BitPerColor) {
Source = (UINT8 *) BltBuffer + (SrcY * Delta) + SourceX * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL);
} else {
for (IndexX = 0; IndexX < Width; IndexX++) {
Blt =
(EFI_GRAPHICS_OUTPUT_BLT_PIXEL *) (
(UINT8 *) BltBuffer +
(SrcY * Delta) +
((SourceX + IndexX) * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL))
);
Uint32 = *(UINT32*) Blt;
*(UINT32*) (Configure->LineBuffer + (IndexX * Configure->BytesPerPixel)) =
(UINT32) (
(((Uint32 << Configure->PixelShl[0]) >> Configure->PixelShr[0]) &
Configure->PixelMasks.RedMask) |
(((Uint32 << Configure->PixelShl[1]) >> Configure->PixelShr[1]) &
Configure->PixelMasks.GreenMask) |
(((Uint32 << Configure->PixelShl[2]) >> Configure->PixelShr[2]) &
Configure->PixelMasks.BlueMask)
);
}
Source = Configure->LineBuffer;
}
CopyMem (Destination, Source, WidthInBytes);
}
return RETURN_SUCCESS;
}
/**
Performs a UEFI Graphics Output Protocol Blt Video to Video operation
@param[in] Configure Pointer to a configuration which was successfully
created by FrameBufferBltConfigure ().
@param[in] SourceX X location within video.
@param[in] SourceY Y location within video.
@param[in] DestinationX X location within video.
@param[in] DestinationY Y location within video.
@param[in] Width Width (in pixels).
@param[in] Height Height.
@retval RETURN_INVALID_PARAMETER Invalid parameter were passed in.
@retval RETURN_SUCCESS The Blt operation was performed successfully.
**/
RETURN_STATUS
FrameBufferBltLibVideoToVideo (
IN FRAME_BUFFER_CONFIGURE *Configure,
IN UINTN SourceX,
IN UINTN SourceY,
IN UINTN DestinationX,
IN UINTN DestinationY,
IN UINTN Width,
IN UINTN Height
)
{
UINT8 *Source;
UINT8 *Destination;
UINTN Offset;
UINTN WidthInBytes;
INTN LineStride;
//
// Video to Video: Source is Video, destination is Video
//
if (SourceY + Height > Configure->Height) {
return RETURN_INVALID_PARAMETER;
}
if (SourceX + Width > Configure->Width) {
return RETURN_INVALID_PARAMETER;
}
if (DestinationY + Height > Configure->Height) {
return RETURN_INVALID_PARAMETER;
}
if (DestinationX + Width > Configure->Width) {
return RETURN_INVALID_PARAMETER;
}
if (Width == 0 || Height == 0) {
return RETURN_INVALID_PARAMETER;
}
WidthInBytes = Width * Configure->BytesPerPixel;
Offset = (SourceY * Configure->PixelsPerScanLine) + SourceX;
Offset = Configure->BytesPerPixel * Offset;
Source = Configure->FrameBuffer + Offset;
Offset = (DestinationY * Configure->PixelsPerScanLine) + DestinationX;
Offset = Configure->BytesPerPixel * Offset;
Destination = Configure->FrameBuffer + Offset;
LineStride = Configure->BytesPerPixel * Configure->PixelsPerScanLine;
if (Destination > Source) {
//
// Copy from last line to avoid source is corrupted by copying
//
Source += Height * LineStride;
Destination += Height * LineStride;
LineStride = -LineStride;
}
while (Height-- > 0) {
CopyMem (Destination, Source, WidthInBytes);
Source += LineStride;
Destination += LineStride;
}
return RETURN_SUCCESS;
}
/**
Performs a UEFI Graphics Output Protocol Blt operation.
@param[in] Configure Pointer to a configuration which was successfully
created by FrameBufferBltConfigure ().
@param[in,out] BltBuffer The data to transfer to screen.
@param[in] BltOperation The operation to perform.
@param[in] SourceX The X coordinate of the source for BltOperation.
@param[in] SourceY The Y coordinate of the source for BltOperation.
@param[in] DestinationX The X coordinate of the destination for
BltOperation.
@param[in] DestinationY The Y coordinate of the destination for
BltOperation.
@param[in] Width The width of a rectangle in the blt rectangle
in pixels.
@param[in] Height The height of a rectangle in the blt rectangle
in pixels.
@param[in] Delta Not used for EfiBltVideoFill and
EfiBltVideoToVideo operation. If a Delta of 0
is used, the entire BltBuffer will be operated
on. If a subrectangle of the BltBuffer is
used, then Delta represents the number of
bytes in a row of the BltBuffer.
@retval RETURN_INVALID_PARAMETER Invalid parameter were passed in.
@retval RETURN_SUCCESS The Blt operation was performed successfully.
**/
RETURN_STATUS
EFIAPI
FrameBufferBlt (
IN FRAME_BUFFER_CONFIGURE *Configure,
IN OUT EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer, OPTIONAL
IN EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation,
IN UINTN SourceX,
IN UINTN SourceY,
IN UINTN DestinationX,
IN UINTN DestinationY,
IN UINTN Width,
IN UINTN Height,
IN UINTN Delta
)
{
if (Configure == NULL) {
return RETURN_INVALID_PARAMETER;
}
switch (BltOperation) {
case EfiBltVideoToBltBuffer:
return FrameBufferBltLibVideoToBltBuffer (
Configure,
BltBuffer,
SourceX,
SourceY,
DestinationX,
DestinationY,
Width,
Height,
Delta
);
case EfiBltVideoToVideo:
return FrameBufferBltLibVideoToVideo (
Configure,
SourceX,
SourceY,
DestinationX,
DestinationY,
Width,
Height
);
case EfiBltVideoFill:
return FrameBufferBltLibVideoFill (
Configure,
BltBuffer,
DestinationX,
DestinationY,
Width,
Height
);
case EfiBltBufferToVideo:
return FrameBufferBltLibBufferToVideo (
Configure,
BltBuffer,
SourceX,
SourceY,
DestinationX,
DestinationY,
Width,
Height,
Delta
);
default:
return RETURN_INVALID_PARAMETER;
}
}