ref: a8debfc9fa0201aabbb6a9c84d98d26ad579a999
parent: 430ffb36d2fb74b1d9163a676fad3ffaa544770e
author: allkern <lisandroaalarcon@gmail.com>
date: Sat Oct 7 15:55:49 EDT 2023
Improve MDEC
--- a/frontend/main.c
+++ b/frontend/main.c
@@ -80,6 +80,7 @@
psx_pad_attach_joy(psx->pad, 0, input);
psx_pad_attach_mcd(psx->pad, 0, "slot1.mcd");
+ psx_pad_attach_mcd(psx->pad, 1, "slot2.mcd");
if (cfg->exe) { while (psx->cpu->pc != 0x80030000) {--- a/psx/dev/dma.c
+++ b/psx/dev/dma.c
@@ -167,14 +167,18 @@
if (!CHCR_BUSY(mdec_in))
return;
- for (int i = 0; i < BCR_SIZE(mdec_in); i++) {- uint32_t data = psx_bus_read32(dma->bus, dma->gpu.madr);
+ size_t size = BCR_SIZE(mdec_in) * BCR_BCNT(mdec_in);
+ for (int i = 0; i < size; i++) {+ uint32_t data = psx_bus_read32(dma->bus, dma->mdec_in.madr);
+
psx_bus_write32(dma->bus, 0x1f801820, data);
- dma->gpu.madr += CHCR_STEP(mdec_in) ? -4 : 4;
+ dma->mdec_in.madr += CHCR_STEP(mdec_in) ? -4 : 4;
}
+ dma->mdec_in_irq_delay = 1;
+
dma->mdec_in.chcr &= ~(CHCR_BUSY_MASK | CHCR_TRIG_MASK);
dma->mdec_in.bcr = 0;
}
@@ -183,7 +187,20 @@
if (!CHCR_BUSY(mdec_out))
return;
- log_fatal("MDEC_OUT DMA channel unimplemented");+ size_t size = BCR_SIZE(mdec_out) * BCR_BCNT(mdec_out);
+
+ for (int i = 0; i < size; i++) {+ uint32_t data = psx_bus_read32(dma->bus, 0x1f801820);
+
+ psx_bus_write32(dma->bus, dma->mdec_out.madr, data);
+
+ dma->mdec_out.madr += CHCR_STEP(mdec_out) ? -4 : 4;
+ }
+
+ dma->mdec_out_irq_delay = 1;
+
+ dma->mdec_out.chcr &= ~(CHCR_BUSY_MASK | CHCR_TRIG_MASK);
+ dma->mdec_out.bcr = 0;
}
void psx_dma_do_gpu_linked(psx_dma_t* dma) {@@ -438,6 +455,20 @@
dma->dicr |= DICR_DMA6FL;
dma->otc_irq_delay = 0;
+ }
+
+ if (dma->mdec_in_irq_delay) {+ if (dma->dicr & DICR_DMA0EN)
+ dma->dicr |= DICR_DMA0FL;
+
+ dma->mdec_in_irq_delay = 0;
+ }
+
+ if (dma->mdec_out_irq_delay) {+ if (dma->dicr & DICR_DMA1EN)
+ dma->dicr |= DICR_DMA1FL;
+
+ dma->mdec_out_irq_delay = 0;
}
int prev_irq_signal = (dma->dicr & DICR_IRQSI) != 0;
--- a/psx/dev/dma.h
+++ b/psx/dev/dma.h
@@ -30,6 +30,8 @@
dma_channel_t pio;
dma_channel_t otc;
+ int mdec_in_irq_delay;
+ int mdec_out_irq_delay;
int cdrom_irq_delay;
int spu_irq_delay;
int gpu_irq_delay;
--- a/psx/dev/mdec.c
+++ b/psx/dev/mdec.c
@@ -5,22 +5,213 @@
#include <string.h>
#include <stdlib.h>
+int zigzag[] = {+ 0 , 1 , 5 , 6 , 14, 15, 27, 28,
+ 2 , 4 , 7 , 13, 16, 26, 29, 42,
+ 3 , 8 , 12, 17, 25, 30, 41, 43,
+ 9 , 11, 18, 24, 31, 40, 44, 53,
+ 10, 19, 23, 32, 39, 45, 52, 54,
+ 20, 22, 33, 38, 46, 51, 55, 60,
+ 21, 34, 37, 47, 50, 56, 59, 61,
+ 35, 36, 48, 49, 57, 58, 62, 63
+};
+
+int zagzig[] = {+ 0, 1, 8, 16, 9, 2, 3, 10,
+ 17, 24, 32, 25, 18, 11, 4, 5,
+ 12, 19, 26, 33, 40, 48, 41, 34,
+ 27, 20, 13, 6, 7, 14, 21, 28,
+ 35, 42, 49, 56, 57, 50, 43, 36,
+ 29, 22, 15, 23, 30, 37, 44, 51,
+ 58, 59, 52, 45, 38, 31, 39, 46,
+ 53, 60, 61, 54, 47, 55, 62, 63
+};
+
+float scalezag[] = {+ 0.125000, 0.173380, 0.173380, 0.163320, 0.240485, 0.163320, 0.146984, 0.226532,
+ 0.226532, 0.146984, 0.125000, 0.203873, 0.213388, 0.203873, 0.125000, 0.098212,
+ 0.173380, 0.192044, 0.192044, 0.173380, 0.098212, 0.067650, 0.136224, 0.163320,
+ 0.172835, 0.163320, 0.136224, 0.067650, 0.034487, 0.093833, 0.128320, 0.146984,
+ 0.146984, 0.128320, 0.093833, 0.034487, 0.047835, 0.088388, 0.115485, 0.125000,
+ 0.115485, 0.088388, 0.047835, 0.045060, 0.079547, 0.098212, 0.098212, 0.079547,
+ 0.045060, 0.040553, 0.067650, 0.077165, 0.067650, 0.040553, 0.034487, 0.053152,
+ 0.053152, 0.034487, 0.027097, 0.036612, 0.027097, 0.018664, 0.018664, 0.009515
+};
+
+#define EXTS10(v) (((int16_t)((v) << 6)) >> 6)
+#define CLAMP(v, l, h) ((v <= l) ? l : ((v >= h) ? h : v))
+#define MAX(a, b) (a > b ? a : b)
+
+void real_idct(int16_t* blk, int16_t* scale) {+ int16_t buf[64];
+
+ int16_t* src = blk;
+ int16_t* dst = buf;
+
+ for (int pass = 0; pass < 2; pass++) {+ for (int x = 0; x < 8; x++) {+ for (int y = 0; y < 8; y++) {+ int sum = 0;
+
+ for (int z = 0; z < 8; z++)
+ sum += src[y+z*8] * (scale[x+z*8] / 8);
+
+ dst[x+y*8] = (sum + 0xfff) / 0x2000;
+ }
+ }
+
+ int16_t* temp = src;
+
+ src = dst;
+ dst = temp;
+ }
+}
+
+#define IDCT_FUNC(blk, scale) real_idct(blk, scale)
+
+uint16_t* rl_decode_block(int16_t* blk, uint16_t* src, uint8_t* quant, int16_t* scale) {+ int k = 0;
+
+ for (int i = 0; i < 64; i++)
+ blk[i] = 0;
+
+ uint16_t n = *src;
+
+ src += 2;
+
+ while (n == 0xfe00) {+ n = *src;
+
+ src += 2;
+ }
+
+ int q_scale = (n >> 10) & 0x3f;
+
+ int16_t val = EXTS10(n & 0x3ff) * quant[k];
+
+ while (k < 64) {+ if (!q_scale)
+ val = EXTS10(n & 0x3ff) * 2;
+
+ val = CLAMP(val, -0x400, 0x3ff);
+ // val *= scalezag[k]; // For fast IDCT
+
+ if (q_scale > 0)
+ blk[zagzig[k]] = val;
+
+ if (!q_scale)
+ blk[k] = val;
+
+ n = *src;
+
+ src += 2;
+
+ k += ((n >> 10) & 0x3f) + 1;
+
+ val = (EXTS10(n & 0x3ff) * quant[k] * q_scale + 4) / 8;
+ }
+
+ IDCT_FUNC(blk, scale);
+
+ return src;
+}
+
+void yuv_to_rgb(psx_mdec_t* mdec, int xx, int yy) {+ for (int y = 0; y < 8; y++) {+ for (int x = 0; x < 8; x++) {+ int16_t r = mdec->crblk[((x + xx) >> 1) + ((y + yy) >> 1) * 8];
+ int16_t b = mdec->cbblk[((x + xx) >> 1) + ((y + yy) >> 1) * 8];
+ int16_t g = (-0.3437 * (float)b) + (-0.7143 * (float)r);
+
+ r = (1.402 * (float)r);
+ b = (1.772 * (float)b);
+
+ int16_t l = mdec->yblk[x + y * 8];
+
+ r = CLAMP(l + r, -128, 127);
+ g = CLAMP(l + g, -128, 127);
+ b = CLAMP(l + b, -128, 127);
+
+ if (!mdec->output_signed) {+ r ^= 0x80;
+ g ^= 0x80;
+ b ^= 0x80;
+ }
+
+ if (mdec->output_depth == 3) {+ uint16_t r5 = ((uint8_t)r) >> 3;
+ uint16_t g5 = ((uint8_t)g) >> 3;
+ uint16_t b5 = ((uint8_t)b) >> 3;
+
+ uint16_t rgb = (b << 10) | (g << 5) | r;
+
+ if (mdec->output_bit15)
+ rgb |= 0x8000;
+
+ mdec->output[0 + ((x + xx) + (y + yy) * 16) * 2] = rgb & 0xff;
+ mdec->output[1 + ((x + xx) + (y + yy) * 16) * 2] = rgb >> 8;
+ } else {+ mdec->output[0 + ((x + xx) + (y + yy) * 16) * 3] = r & 0xff;
+ mdec->output[1 + ((x + xx) + (y + yy) * 16) * 3] = g & 0xff;
+ mdec->output[2 + ((x + xx) + (y + yy) * 16) * 3] = b & 0xff;
+ }
+ }
+ }
+}
+
void mdec_nop(psx_mdec_t* mdec) { /* Do nothing */ } void mdec_decode_macroblock(psx_mdec_t* mdec) {- // To-do
+ if (mdec->output_depth < 2) {+ rl_decode_block(mdec->yblk, mdec->input, mdec->y_quant_table, mdec->scale_table);
+
+ for (int i = 0; i < 64; i++) {+ int16_t y = mdec->yblk[i] & 0xff;
+
+ if (mdec->output_depth == 1) {+ mdec->output[i] = y;
+ } else {+ // To-do
+ mdec->output[i] = 0;
+ }
+ }
+
+ mdec->output_words_remaining = ((mdec->output_depth == 1) ? 64 : 32) >> 2;
+ mdec->output_empty = 0;
+ mdec->output_index = 0;
+ } else {+ uint16_t* ptr = mdec->input;
+
+ ptr = rl_decode_block(mdec->crblk, ptr, mdec->uv_quant_table, mdec->scale_table);
+ ptr = rl_decode_block(mdec->cbblk, ptr, mdec->uv_quant_table, mdec->scale_table);
+ ptr = rl_decode_block(mdec->yblk, ptr, mdec->y_quant_table, mdec->scale_table);
+ yuv_to_rgb(mdec, 0, 0);
+ ptr = rl_decode_block(mdec->yblk, ptr, mdec->y_quant_table, mdec->scale_table);
+ yuv_to_rgb(mdec, 0, 8);
+ ptr = rl_decode_block(mdec->yblk, ptr, mdec->y_quant_table, mdec->scale_table);
+ yuv_to_rgb(mdec, 8, 0);
+ ptr = rl_decode_block(mdec->yblk, ptr, mdec->y_quant_table, mdec->scale_table);
+ yuv_to_rgb(mdec, 8, 8);
+
+ mdec->output_words_remaining = ((mdec->output_depth == 3) ? 512 : 768) >> 2;
+ mdec->output_empty = 0;
+ mdec->output_index = 0;
+
+ log_set_quiet(0);
+ log_fatal("Finished decoding %u-bit MDEC data", (mdec->output_depth == 3) ? 15 : 24);+ log_set_quiet(1);
+ }
}
void mdec_set_iqtab(psx_mdec_t* mdec) {- mdec->state = mdec->cmd & 1 ? MDEC_RECV_QUANT_COLOR : MDEC_RECV_QUANT;
- mdec->data_remaining = mdec->cmd & 1 ? 32 : 16;
- mdec->index = 0;
+ memcpy(mdec->y_quant_table, mdec->input, 64);
+
+ if (mdec->recv_color)
+ memcpy(mdec->uv_quant_table, &mdec->input[16], 64);
}
void mdec_set_scale(psx_mdec_t* mdec) {- mdec->state = MDEC_RECV_SCALE;
- mdec->data_remaining = 32;
- mdec->index = 0;
+ memcpy(mdec->scale_table, mdec->input, 128);
}
mdec_fn_t g_mdec_cmd_table[] = {@@ -34,39 +225,6 @@
mdec_nop
};
-void mdec_recv_cmd(psx_mdec_t* mdec) {- log_fatal("MDEC command %u (%08x)", mdec->cmd >> 29, mdec->cmd);-
- g_mdec_cmd_table[mdec->cmd >> 29](mdec);
-}
-void mdec_recv_block(psx_mdec_t* mdec) {}-void mdec_recv_quant(psx_mdec_t* mdec) {- mdec->data_remaining--;
-
- if (!mdec->data_remaining)
- mdec->state = MDEC_RECV_CMD;
-}
-void mdec_recv_quant_color(psx_mdec_t* mdec) {- mdec->data_remaining--;
-
- if (!mdec->data_remaining)
- mdec->state = MDEC_RECV_CMD;
-}
-void mdec_recv_scale(psx_mdec_t* mdec) {- mdec->data_remaining--;
-
- if (!mdec->data_remaining)
- mdec->state = MDEC_RECV_CMD;
-}
-
-mdec_fn_t g_mdec_recv_table[] = {- mdec_recv_cmd,
- mdec_recv_block,
- mdec_recv_quant,
- mdec_recv_quant_color,
- mdec_recv_scale
-};
-
psx_mdec_t* psx_mdec_create() {return (psx_mdec_t*)malloc(sizeof(psx_mdec_t));
}
@@ -83,9 +241,35 @@
uint32_t psx_mdec_read32(psx_mdec_t* mdec, uint32_t offset) { switch (offset) { case 0: {+ if (mdec->output_words_remaining) {+ --mdec->output_words_remaining;
+ log_set_quiet(0);
+ log_fatal("output read %08x", 0);+ log_set_quiet(1);
+
+ return ((uint32_t*)mdec->output)[mdec->output_index++];
+ } else {+ mdec->output_empty = 1;
+ mdec->output_index = 0;
+ }
} break;
- case 4: return mdec->status;
+ case 4: {+ uint32_t status = 0;
+
+ status |= mdec->words_remaining;
+ status |= mdec->current_block << 16;
+ status |= mdec->output_bit15 << 23;
+ status |= mdec->output_signed << 24;
+ status |= mdec->output_depth << 25;
+ status |= mdec->output_request << 27;
+ status |= mdec->input_request << 28;
+ status |= mdec->busy << 29;
+ status |= mdec->input_full << 30;
+ status |= mdec->output_empty << 31;
+
+ return 0x00000000;
+ } break;
}
return 0x0;
@@ -106,17 +290,97 @@
void psx_mdec_write32(psx_mdec_t* mdec, uint32_t offset, uint32_t value) { switch (offset) { case 0: {+ if (mdec->busy) {+ mdec->input[mdec->input_index++] = value;
+
+ --mdec->words_remaining;
+
+ if (!mdec->words_remaining) {+ mdec->output_empty = 0;
+ mdec->input_full = 1;
+ mdec->busy = 0;
+
+ g_mdec_cmd_table[mdec->cmd >> 29](mdec);
+
+ free(mdec->input);
+ }
+
+ break;
+ }
+
mdec->cmd = value;
+ mdec->output_empty = 1;
+ mdec->output_bit15 = (value >> 25) & 1;
+ mdec->output_signed = (value >> 26) & 1;
+ mdec->output_depth = (value >> 27) & 3;
+ mdec->input_index = 0;
+ mdec->busy = 1;
- g_mdec_recv_table[mdec->state](mdec);
+ log_set_quiet(0);
+ switch (mdec->cmd >> 29) {+ case MDEC_CMD_NOP: {+ mdec->busy = 0;
+ mdec->words_remaining = 0;
+
+ log_fatal("MDEC %08x: NOP", mdec->cmd);+ } break;
+
+ case MDEC_CMD_DECODE: {+ mdec->words_remaining = mdec->cmd & 0xffff;
+
+ log_fatal("MDEC %08x: decode macroblock %04x",+ mdec->cmd,
+ mdec->words_remaining
+ );
+ } break;
+
+ case MDEC_CMD_SET_QT: {+ mdec->recv_color = mdec->cmd & 1;
+ mdec->words_remaining = mdec->recv_color ? 32 : 16;
+
+ log_fatal("MDEC %08x: set quant tables %04x",+ mdec->cmd,
+ mdec->words_remaining
+ );
+ } break;
+
+ case MDEC_CMD_SET_ST: {+ mdec->words_remaining = 32;
+
+ log_fatal("MDEC %08x: set scale table %04x",+ mdec->cmd,
+ mdec->words_remaining
+ );
+ } break;
+ }
+ log_set_quiet(1);
+
+ if (mdec->words_remaining) {+ mdec->input_full = 0;
+ mdec->input_index = 0;
+ mdec->input = malloc(mdec->words_remaining * sizeof(uint32_t));
+ }
} break;
case 4: {- if (value & 0x80000000)
- mdec->status = 0x80040000;
+ // Reset
+ if (value & 0x80000000) {+ // status = 80040000h
+ mdec->busy = 0;
+ mdec->words_remaining = 0;
+ mdec->output_bit15 = 0;
+ mdec->output_signed = 0;
+ mdec->output_depth = 0;
+ mdec->input_request = 0;
+ mdec->output_request = 0;
+ mdec->busy = 0;
+ mdec->input_full = 0;
+ mdec->output_empty = 1;
+ mdec->current_block = 4;
+ }
- mdec->status &= 0xe7ffffff;
- mdec->status |= (value & 0x60000000) >> 2;
+ mdec->output_request = (value & 0x20000000) != 0;
+ mdec->input_request = (value & 0x40000000) != 0;
} break;
}
@@ -137,4 +401,7 @@
void psx_mdec_destroy(psx_mdec_t* mdec) {free(mdec);
-}
\ No newline at end of file
+}
+
+#undef CLAMP
+#undef MAX
\ No newline at end of file
--- a/psx/dev/mdec.h
+++ b/psx/dev/mdec.h
@@ -9,6 +9,9 @@
#define PSX_MDEC_BEGIN 0x1f801820
#define PSX_MDEC_END 0x1f801827
+#define MDEC_SCALE_TABLE_SIZE 64
+#define MDEC_QUANT_TABLE_SIZE 64
+
enum {MDEC_RECV_CMD,
MDEC_RECV_BLOCK,
@@ -17,6 +20,27 @@
MDEC_RECV_SCALE
};
+/*
+ 31 Data-Out Fifo Empty (0=No, 1=Empty)
+ 30 Data-In Fifo Full (0=No, 1=Full, or Last word received)
+ 29 Command Busy (0=Ready, 1=Busy receiving or processing parameters)
+ 28 Data-In Request (set when DMA0 enabled and ready to receive data)
+ 27 Data-Out Request (set when DMA1 enabled and ready to send data)
+ 26-25 Data Output Depth (0=4bit, 1=8bit, 2=24bit, 3=15bit) ;CMD.28-27
+ 24 Data Output Signed (0=Unsigned, 1=Signed) ;CMD.26
+ 23 Data Output Bit15 (0=Clear, 1=Set) (for 15bit depth only) ;CMD.25
+ 22-19 Not used (seems to be always zero)
+ 18-16 Current Block (0..3=Y1..Y4, 4=Cr, 5=Cb) (or for mono: always 4=Y)
+ 15-0 Number of Parameter Words remaining minus 1 (FFFFh=None) ;CMD.Bit0-15
+*/
+
+enum {+ MDEC_CMD_NOP,
+ MDEC_CMD_DECODE,
+ MDEC_CMD_SET_QT,
+ MDEC_CMD_SET_ST
+};
+
typedef struct {uint32_t io_base, io_size;
@@ -25,6 +49,33 @@
int state;
int data_remaining;
int index;
+
+ uint32_t* input;
+ int input_index;
+
+ uint8_t output[(16 * 16) * 3];
+ int output_index;
+ uint16_t output_words_remaining;
+
+ uint16_t words_remaining;
+ int current_block;
+ int output_bit15;
+ int output_signed;
+ int output_depth;
+ int input_request;
+ int output_request;
+ int busy;
+ int input_full;
+ int output_empty;
+
+ int recv_color;
+ uint8_t uv_quant_table[MDEC_QUANT_TABLE_SIZE];
+ uint8_t y_quant_table[MDEC_QUANT_TABLE_SIZE];
+ int16_t scale_table[MDEC_SCALE_TABLE_SIZE];
+
+ int16_t yblk[64];
+ int16_t crblk[64];
+ int16_t cbblk[64];
uint32_t status;
} psx_mdec_t;
--- a/psx/dev/pad.c
+++ b/psx/dev/pad.c
@@ -9,7 +9,7 @@
psx_input_t* joy = pad->joy_slot[(pad->ctrl >> 13) & 1];
psx_mcd_t* mcd = pad->mcd_slot[(pad->ctrl >> 13) & 1];
- if ((!(joy || mcd)) || !pad->dest)
+ if (!pad->dest)
return 0xffffffff;
if (!(pad->ctrl & CTRL_JOUT) && !(pad->ctrl & CTRL_RXEN))
@@ -17,6 +17,12 @@
switch (pad->dest) { case DEST_JOY: {+ if (!joy) {+ pad->dest = 0;
+
+ return 0xffffffff;
+ }
+
uint8_t data = joy->read_func(joy->udata);
if (!joy->query_fifo_func(joy->udata))
@@ -26,6 +32,12 @@
} break;
case DEST_MCD: {+ if (!mcd) {+ pad->dest = 0;
+
+ return 0xffffffff;
+ }
+
uint8_t data = psx_mcd_read(mcd);
if (!psx_mcd_query(mcd))
@@ -42,18 +54,25 @@
psx_input_t* joy = pad->joy_slot[(pad->ctrl >> 13) & 1];
psx_mcd_t* mcd = pad->mcd_slot[(pad->ctrl >> 13) & 1];
- if ((!(joy || mcd)) || !(pad->ctrl & CTRL_TXEN))
+ if (!(pad->ctrl & CTRL_TXEN))
return;
- if (pad->ctrl & CTRL_ACIE)
- pad->cycles_until_irq = 1500;
-
if (!pad->dest) {- if ((data == DEST_JOY) || (data == DEST_MCD))
+ if ((data == DEST_JOY) || (data == DEST_MCD)) {pad->dest = data;
+
+ if (pad->ctrl & CTRL_ACIE)
+ pad->cycles_until_irq = 1500;
+ }
} else { switch (pad->dest) { case DEST_JOY: {+ if (!joy) {+ pad->dest = 0;
+
+ return;
+ }
+
joy->write_func(joy->udata, data);
if (!joy->query_fifo_func(joy->udata))
@@ -61,6 +80,12 @@
} break;
case DEST_MCD: {+ if (!mcd) {+ pad->dest = 0;
+
+ return;
+ }
+
psx_mcd_write(mcd, data);
if (!psx_mcd_query(mcd))
@@ -67,6 +92,9 @@
pad->dest = 0;
} break;
}
+
+ if (pad->ctrl & CTRL_ACIE)
+ pad->cycles_until_irq = 1500;
}
}
--- a/psx/dev/pad.h
+++ b/psx/dev/pad.h
@@ -7,6 +7,8 @@
#include "input.h"
#include "mcd.h"
+#include "../input/sda.h"
+
#define PSX_PAD_BEGIN 0x1f801040
#define PSX_PAD_SIZE 0x10
#define PSX_PAD_END 0x1f80104f
@@ -93,6 +95,16 @@
enum {DEST_JOY = 0x01,
DEST_MCD = 0x81
+};
+
+enum {+ PAD_CONTROLLER_SDA,
+ PAD_CONTROLLER_MOUSE,
+ PAD_CONTROLLER_NAMCO_VOLUME,
+ PAD_CONTROLLER_SANKYO_NASUKA,
+ PAD_WHEEL_NEGCON,
+ PAD_WHEEL_MADCATZ,
+ PAD_WHEEL_MADCATZ_MC2
};
/*
--- a/psx/dev/spu.c
+++ b/psx/dev/spu.c
@@ -524,4 +524,7 @@
uint16_t clampr = CLAMP(right, INT16_MIN, INT16_MAX);
return clampl | (((uint32_t)clampr) << 16);
-}
\ No newline at end of file
+}
+
+#undef CLAMP
+#undef MAX
\ No newline at end of file
--
⑨