diff --git a/code/blitblit.h b/code/blitblit.h index 4fdd1ea9..3042d3f4 100644 --- a/code/blitblit.h +++ b/code/blitblit.h @@ -2308,179 +2308,3 @@ class BlitTranslucentWriteAlpha : public Blitter { T const * TranslateTable; }; - -/* -** Assembly versions of some of the templated blitter object functions. Borland and -** Visual C++ support a compatible inline-assembly formats. However, Borland compiler -** does not allow inline-assembly to be part of an inline function -- go figure. -** It will still compile, it just generates warning messages. -*/ -#if defined(_MSC_VER) - -#pragma optimize("", off) - -template <> -inline void BlitTrans::BlitForward(void * dest, void const * source, int len, int z_min, void *z_buff, void *a_buff, int alpha_level, int warp_offset) const -{ - __asm { - mov esi,[source] - mov edi,[dest] - mov ecx,[len] - dec edi - inc ecx - - again: - dec ecx - jz fini - mov al,[esi] - inc edi - inc esi - test al,al - jz again - mov [edi],al - jmp again - - fini: - } -} - - -template <> -inline void BlitTransXlat::BlitForward(void * dest, void const * source, int len, int z_min, void *z_buff, void *a_buff, int alpha_level, int warp_offset) const -{ - unsigned short const * xlator = TranslateTable; - - __asm { - mov ebx,[xlator] - mov ecx,[len] - inc ecx - mov edi,[dest] - sub edi,2 - mov esi,[source] - xor eax,eax - - again: - dec ecx - jz over - add edi,2 - mov al,[esi] - inc esi - or al,al - jz again - mov dx,[ebx+eax*2] - mov [edi],dx - jmp again - - over: - } -} - - -template <> -inline void BlitTransRemapXlat::BlitForward(void * dest, void const * source, int len, int z_min, void *z_buff, void *a_buff, int alpha_level, int warp_offset) const -{ - unsigned short const * translator = TranslateTable; - unsigned char const * remapper = RemapTable; - - __asm { - mov ecx,[len] - mov edi,[dest] - sub edi,2 - mov esi,[source] - mov ebx,[remapper] - mov edx,[translator] - xor eax,eax - - again: - /* - ** This block is 11 cycles per pixel, if not transparent, and 5 - ** cycles per pixel, if transparent. - */ - dec ecx - jz over - add edi,2 - xor eax,eax - lodsb - or al,al - jz again - mov al,[ebx+eax] // First remap step (8 bit to 8 bit). - mov ax,[edx+eax*2] // Second remap step (8 bit to 16 bit). - mov [edi],ax - jmp again - - over: - } -} - - -#if 0 -template <> -inline void BlitTransZRemapXlat::BlitForward(void * dest, void const * source, int len, int z_min, void *z_buff, void *a_buff, int alpha_level, int warp_offset) const -{ - unsigned short const * translator = TranslateTable; - unsigned char const * remapper = *RemapTable; - - __asm { - mov ecx,[len] - mov edi,[dest] - sub edi,2 - mov esi,[source] - mov ebx,[remapper] - mov edx,[translator] - xor eax,eax - } - - /* - ** This block is 11 cycles per pixel, if not transparent, and 5 - ** cycles per pixel, if transparent. - */ -again: - __asm { - dec ecx - jz over - add edi,2 - xor eax,eax - lodsb - or al,al - jz again - mov al,[ebx+eax] // First remap step (8 bit to 8 bit). - mov ax,[edx+eax*2] // Second remap step (8 bit to 16 bit). - mov [edi],ax - jmp again - } -over:; -} - - -template <> -inline void BlitPlainXlat::BlitForward(void * dest, void const * source, int len, int z_min, void *z_buff, void *a_buff, int alpha_level, int warp_offset) const -{ - unsigned short const * remapper = TranslateTable; - __asm { - mov ebx,[remapper] - mov ecx,[len] - mov esi,[source] - mov edi,[dest] - sub edi,2 - } -again: - /* - ** This block processes pixels at 7 clocks per pixel. - */ - __asm { - xor eax,eax - add edi,2 - mov al,[esi] - inc esi - mov ax,[ebx+eax*2] - mov [edi],ax - dec ecx - jnz again - } -} - - -#endif -#endif - -#pragma optimize("", on) diff --git a/code/lcw.cpp b/code/lcw.cpp index 82f5b5c5..4509c781 100644 --- a/code/lcw.cpp +++ b/code/lcw.cpp @@ -34,6 +34,9 @@ #include "always.h" #include "lcw.h" +#include +#include + /// /// Decompresses an LCW encoded data block. @@ -188,9 +191,6 @@ uint32_t LCW_Uncomp(void const * source, void * dest, unsigned long length) } -#if defined(_MSC_VER) - - /*********************************************************************************************** * LCW_Comp -- Performes LCW compression on a block of data. * * * @@ -214,232 +214,192 @@ uint32_t LCW_Uncomp(void const * source, void * dest, unsigned long length) * HISTORY: * * 05/20/1997 JLB : Created. * *=============================================================================================*/ -/// Renegade guards this body with #ifdef _WINDOWS, which would force retval ahead of the -/// other locals; that shape is not reproduced here. -/*ARGSUSED*/ + +/* + * Compressed blocks reach save games, so this emits the same bytes the assembly it replaced + * emitted, quirks included. Two of those quirks are worth knowing about before changing + * anything here. + * + * A datasize of 1 does not produce a one byte block. The first source byte is written before + * the loop is entered and the end is only tested after a byte has been consumed, so a second + * byte is read past the end of the source and encoded alongside it. The block that comes out + * expands to two bytes rather than one. + * + * The search for a run reads sixty four bytes ahead of the current position without checking + * that they belong to the source at all, so it can read past the end of the buffer. Only the + * comparison result is used, and the run length that follows is measured properly. + */ int LCW_Comp(void const * source, void * dest, int datasize) { - int inlen = 0; - int a1stdest = 0; - int a1stsrc = 0; - int lenoff = 0; - int ndest = 0; - int count = 0; - int matchoff = 0; - int end_of_data = 0; - int retval = 0; -#ifdef _DEBUG - inlen = inlen; - a1stdest = a1stdest; - a1stsrc = a1stsrc; - lenoff = lenoff; - ndest = ndest; - count = count; - matchoff = matchoff; - end_of_data = end_of_data; -#endif - - __asm { - cld // make sure all string commands are forward - mov edi,[dest] - mov esi,[source] - mov edx,[datasize] // get length of data to compress - -// compress data to the following codes in the format b = byte, w = word -// n = byte code pulled from compressed data -// Bit field of n command description -// n=0xxxyyyy,yyyyyyyy short run back y bytes and run x+3 -// n=10xxxxxx,n1,n2,...,nx+1 med length copy the next x+1 bytes -// n=11xxxxxx,w1 med run run x+3 bytes from offset w1 -// n=11111111,w1,w2 long run run w1 bytes from offset w2 -// n=10000000 end end of data reached - - mov ebx,esi - add ebx,edx - mov [end_of_data],ebx - mov [inlen],1 //; set the in-length flag - mov [a1stdest],edi //; save original dest offset for size calc - mov [a1stsrc],esi //; save offset of first byte of data - mov [lenoff],edi //; save the offset of the legth of this len - sub eax,eax - mov al,081h //; the first byte is always a len - stosb //; write out a len of 1 - lodsb //; get the byte - stosb //; save it - - loopstart: - mov [ndest],edi //; save offset of compressed data - mov edi,[a1stsrc] //; get the offset to the first byte of data - mov [count],1 //; set the count of run to 0 - - searchloop: - sub eax,eax - mov al,[esi] //; get the current byte of data - cmp al,[esi+64] - jne short notrunlength - - mov ebx,edi - - mov edi,esi - mov ecx,[end_of_data] - sub ecx,edi - repe scasb - dec edi - mov ecx,edi - sub ecx,esi - cmp ecx,65 - jb short notlongenough - - mov [inlen],0 //; clear the in-length flag -// mov [DWORD PTR inlen],0 //; clear the in-length flag - mov esi,edi - mov edi,[ndest] //; get the offset of our compressed data - - mov ah,al - mov al,0FEh - stosb - xchg ecx,eax - stosw - mov al,ch - stosb - - mov [ndest],edi //; save offset of compressed data - mov edi,ebx - jmp searchloop - - notlongenough: - mov edi,ebx - - notrunlength: - oploop: - mov ecx,esi //; get the address of the last byte +1 - sub ecx,edi //; get the total number of bytes left to comp - jz short searchdone - - repne scasb //; look for a match - jne short searchdone //; if we don't find one we're done - - mov ebx,[count] - mov ah,[esi+ebx-1] - cmp ah,[edi+ebx-2] - - jne oploop - - mov edx,esi //; save this spot for the next search - mov ebx,edi //; save this spot for the length calc - dec edi //; back up one for compare - mov ecx,[end_of_data] //; get the end of data - sub ecx,esi //; sub current source for max len - - repe cmpsb //; see how many bytes match - - jne short notend //; if found mismatch then di - bx = match count - - inc edi //; else cx = 0 and di + 1 - bx = match count - - notend: - mov esi,edx //; restore si - mov eax,edi //; get the dest - sub eax,ebx //; sub the start for total bytes that match - mov edi,ebx //; restore dest - cmp eax,[count] //; see if its better than before - jb searchloop //; if not keep looking - - mov [count],eax //; if so keep the count - dec ebx //; back it up for the actual match offset - mov [matchoff],ebx //; save the offset for later - jmp searchloop //; loop until we searched it all - - searchdone: - mov ecx,[count] //; get the count of the longest run - mov edi,[ndest] //; get the offset of our compressed data - cmp ecx,2 //; see if its not enough run to matter - jbe short lenin //; if its 0,1, or 2 its too small - - cmp ecx,10 //; if not, see if it would fit in a short - ja short medrun //; if not, see if its a medium run - - mov eax,esi //; if its short get the current address - sub eax,[matchoff] //; sub the offset of the match - cmp eax,0FFFh //; if its less than 12 bits its a short - ja short medrun //; if its not, its a medium - - //shortrun: - sub ebx,ebx - mov bl,cl //; get the length (3-10) - sub bl,3 //; sub 3 for a 3 bit number 0-7 - shl bl,4 //; shift it left 4 - add ah,bl //; add in the length for the high nibble - xchg ah,al //; reverse the bytes for a word store - jmp short srunnxt //; do the run fixup code - - medrun: - cmp ecx,64 //; see if its a short run - ja short longrun //; if not, oh well at least its long - - sub cl,3 //; back down 3 to keep it in 6 bits - or cl,0C0h //; the highest bits are always on - mov al,cl //; put it in al for the stosb - stosb //; store it - jmp short medrunnxt //; do the run fixup code - - lenin: - cmp [inlen],0 //; is it doing a length? -// cmp [DWORD PTR inlen],0 //; is it doing a length? - jnz short len //; if so, skip code - - lenin1: - mov [lenoff],edi //; save the length code offset - mov al,80h //; set the length to 0 - stosb //; save it - - len: - mov ebx,[lenoff] //; get the offset of the length code - cmp byte ptr [ebx],0BFh //; see if its maxed out -// cmp [BYTE PTR ebx],0BFh //; see if its maxed out - je lenin1 //; if so put out a new len code - - //stolen: - inc byte ptr [ebx] //; inc the count code -// inc [BYTE PTR ebx] //; inc the count code - lodsb //; get the byte - stosb //; store it - mov [inlen],1 //; we are now in a length so save it -// mov [DWORD PTR inlen],1 //; we are now in a length so save it - jmp short nxt //; do the next code - - longrun: - mov al,0ffh //; its a long so set a code of FF - stosb //; store it - - mov eax,[count] //; send out the count - stosw //; store it - - medrunnxt: - mov eax,[matchoff] //; get the offset - sub eax,[a1stsrc] //; make it relative tot he start of data - - srunnxt: - stosw //; store it - //; this code common to all runs - add esi,[count] //; add in the length of the run to the source - mov [inlen],0 //; set the in leght flag to false -// mov [DWORD PTR inlen],0 //; set the in leght flag to false - - nxt: - cmp esi,[end_of_data] //; see if we did the whole pic - jae short outofhere //; if so, cool! were done - - jmp loopstart - - outofhere: - mov ax,080h //; remember to send an end of data code - stosb //; store it - mov eax,edi //; get the last compressed address - sub eax,[a1stdest] //; sub the first for the compressed size - mov [retval],eax + uint8_t const * const start = static_cast(source); + uint8_t * const first = static_cast(dest); + uint8_t const * const end_of_data = start + datasize; + + uint8_t const * si = start; + uint8_t * di = first; + + /* + * The first command is always a run of literals, opened here and extended in place as + * more of them are emitted. + */ + bool inlen = true; + uint8_t * lenoff = di; + + *di++ = 0x81; + *di++ = *si++; + + while (true) { + uint8_t * ndest = di; + uint8_t const * search = start; + uint8_t const * matchoff = start; + ptrdiff_t count = 1; + + /* + * Find the longest run of earlier data that repeats at the current position. A + * single byte repeated far enough is worth a command of its own and is emitted + * straight away, without disturbing the search. + */ + while (true) { + uint8_t const value = *si; + + if (value == si[64]) { + ptrdiff_t const left = end_of_data - si; + ptrdiff_t matched = 0; + + while (matched < left && si[matched] == value) { + matched++; + } + + /* + * A run that reaches the end of the source is counted one short, + * because the scan it replaces stepped past the last byte it read. + * With nothing left at all that count goes negative, and the test + * below is unsigned, so it reads as enormous and a run is emitted + * from a position that has already passed the end. That only arises + * on the malformed tail described above the function, and it is kept + * because the bytes it produces are the bytes callers have. + */ + ptrdiff_t const runlength = (matched < left) ? matched : (left - 1); + + if (static_cast(runlength) >= 65) { + inlen = false; + si += runlength; + di = ndest; + + *di++ = 0xFE; + *di++ = static_cast(runlength & 0xFF); + *di++ = static_cast((runlength >> 8) & 0xFF); + *di++ = value; + + ndest = di; + continue; + } + } + + ptrdiff_t const window = si - search; + + if (window <= 0) { + break; + } + + /* + * Look for somewhere earlier the current byte appears. + */ + uint8_t const * found = nullptr; + + for (ptrdiff_t i = 0; i < window; i++) { + uint8_t const candidate = *search++; + + if (candidate == value) { + found = search; + break; + } + } + + if (found == nullptr) { + break; + } + + /* + * Reject the candidate cheaply before measuring it: if the byte that would + * end a run at least as long as the best so far does not agree, it cannot + * beat it. + */ + if (si[count - 1] != search[count - 2]) { + continue; + } + + ptrdiff_t const room = end_of_data - si; + ptrdiff_t length = 0; + + while (length < room && si[length] == (search - 1)[length]) { + length++; + } + + if (length < count) { + continue; + } + + count = length; + matchoff = search - 1; + } + + di = ndest; + + if (count > 2) { + size_t const back = static_cast(si - matchoff); + + if (count <= 10 && back <= 0x0FFF) { + + /* + * Short run: three bits of length and twelve of distance, packed into + * two bytes. + */ + *di++ = static_cast((static_cast(count - 3) << 4) | ((back >> 8) & 0x0F)); + *di++ = static_cast(back & 0xFF); + } else { + if (count <= 64) { + *di++ = static_cast(0xC0 | (count - 3)); + } else { + *di++ = 0xFF; + *di++ = static_cast(count & 0xFF); + *di++ = static_cast((count >> 8) & 0xFF); + } + + /* + * The longer forms carry the match's position from the start of the + * data rather than its distance back from here. + */ + size_t const offset = static_cast(matchoff - start); + + *di++ = static_cast(offset & 0xFF); + *di++ = static_cast((offset >> 8) & 0xFF); + } + + si += count; + inlen = false; + } else { + + /* + * Nothing worth referencing, so the byte goes out as a literal. A length + * command counts up to 0x3F bytes before another has to be opened. + */ + if (!inlen || *lenoff == 0xBF) { + lenoff = di; + *di++ = 0x80; + } + + (*lenoff)++; + *di++ = *si++; + inlen = true; + } + + if (si >= end_of_data) { + break; + } } - return(retval); + *di++ = 0x80; + + return(static_cast(di - first)); } -#endif diff --git a/code/mpu.cpp b/code/mpu.cpp index 6c562329..316273cb 100644 --- a/code/mpu.cpp +++ b/code/mpu.cpp @@ -36,6 +36,7 @@ #include "win.h" +#include #include typedef union { @@ -114,8 +115,6 @@ unsigned int Get_CPU_Clock(unsigned int & high) ** */ -#define ASM_RDTSC _asm _emit 0x0f _asm _emit 0x31 - // Max # of samplings to allow before giving up and returning current average. #define MAX_TRIES 20 #define ROUND_THRESHOLD 6 @@ -134,12 +133,10 @@ static unsigned long TSC_High; /// Only call this routine on a processor that supports the RDTSC opcode. void RDTSC(void) { - _asm - { - ASM_RDTSC; - mov TSC_Low, eax - mov TSC_High, edx - } + unsigned long long const stamp = __rdtsc(); + + TSC_Low = (unsigned long)(stamp & 0xFFFFFFFF); + TSC_High = (unsigned long)(stamp >> 32); } @@ -216,8 +213,7 @@ int Get_RDTSC_CPU_Speed(void) QueryPerformanceCounter(&t1); } - ASM_RDTSC; - _asm mov stamp0, EAX + stamp0 = (unsigned long)(__rdtsc() & 0xFFFFFFFF); t0.LowPart = t1.LowPart; // Reset Initial Time t0.HighPart = t1.HighPart; @@ -230,8 +226,7 @@ int Get_RDTSC_CPU_Speed(void) QueryPerformanceCounter(&t1); } - ASM_RDTSC; - _asm mov stamp1, EAX + stamp1 = (unsigned long)(__rdtsc() & 0xFFFFFFFF); cycles = stamp1 - stamp0; // # of cycles passed between reads diff --git a/code/rlerle.h b/code/rlerle.h index 1b2aaacb..59a37f2e 100644 --- a/code/rlerle.h +++ b/code/rlerle.h @@ -2769,259 +2769,3 @@ class RLEBlitTransLucent75ZReadWrite : public RLEBlitter T Mask; }; - -#if 0 - -#if defined(_MSC_VER) -void RLEBlitTransZRemapXlat::Blit(void * dest, void const * source, int len, int leadskip) const -{ - unsigned char const * remapper = *RemapTable; - unsigned short const * transtable = TranslateTable; - - /* - ** Set up the working registers for the blit operation. - */ - __asm { - mov ecx,[len] - mov edi,[dest] - mov esi,[source] - mov ebx,[remapper] - mov edx,[leadskip] - xor eax,eax - } - - /* - ** Skip leading pixels by analyzing the RLE data until the entire - ** requested skip pixel count has been processed. This could result in - ** unprocessed transparent pixels if it ended up in the middle of - ** a transparent pixel run. This is handled in the next block. - */ -moreskip: - __asm { - test edx,edx - jle nomoreskip - dec edx - lodsb - test al,al - jnz moreskip - lodsb - sub edx,eax - inc edx - jmp moreskip - } -nomoreskip: - - /* - ** Handle any left over transparent pixels that would be part of - ** a transparent pixel run that occurs at the end of the leading - ** pixel skip process. - */ - __asm { - neg edx - sub ecx,edx // Account for any left over transparent pixels - lea edi,[edi+edx*2] - mov edx,[transtable] - } - - /* - ** Output the pixel data to the destination. - */ -moredata: - __asm { - xor eax,eax - or ecx,ecx - jle fini - lodsb - test al,al - jz transparent - mov al,[ebx+eax] - mov ax,[edx+eax*2] - dec ecx - stosw - jmp moredata - } - - /* - ** A transparent pixel run just causes the destination pointer - ** and length count to be adjusted by the length of the run. - */ -transparent: - __asm { - lodsb - lea edi,[edi+eax*2] - sub ecx,eax - jmp moredata - } - -fini:; -} - - -void RLEBlitTransRemapXlat::Blit(void * dest, void const * source, int len, int leadskip) const -{ - unsigned char const * remapper = RemapTable; - unsigned short const * transtable = TranslateTable; - - /* - ** Set up the working registers for the blit operation. - */ - __asm { - mov ecx,[len] - mov edi,[dest] - mov esi,[source] - mov ebx,[remapper] - mov edx,[leadskip] - xor eax,eax - } - - /* - ** Skip leading pixels by analyzing the RLE data until the entire - ** requested skip pixel count has been processed. This could result in - ** unprocessed transparent pixels if it ended up in the middle of - ** a transparent pixel run. This is handled in the next block. - */ -moreskip: - __asm { - test edx,edx - jle nomoreskip - dec edx - lodsb - test al,al - jnz moreskip - lodsb - sub edx,eax - inc edx - jmp moreskip - } -nomoreskip: - - /* - ** Handle any left over transparent pixels that would be part of - ** a transparent pixel run that occurs at the end of the leading - ** pixel skip process. - */ - __asm { - neg edx - sub ecx,edx // Account for any left over transparent pixels - lea edi,[edi+edx*2] - mov edx,[transtable] - } - - /* - ** Output the pixel data to the destination. - */ -moredata: - __asm { - xor eax,eax - or ecx,ecx - jle fini - lodsb - test al,al - jz transparent - mov al,[ebx+eax] - mov ax,[edx+eax*2] - dec ecx - stosw - jmp moredata - } - - /* - ** A transparent pixel run just causes the destination pointer - ** and length count to be adjusted by the length of the run. - */ -transparent: - __asm { - lodsb - lea edi,[edi+eax*2] - sub ecx,eax - jmp moredata - } - -fini:; -} - - -void RLEBlitTransXlat::Blit(void * dest, void const * source, int len, int leadskip) const -{ - unsigned short const * transtable = TranslateTable; - - /* - ** Set up the working registers for the blit operation. - */ - __asm { - mov ecx,[len] - mov edi,[dest] - mov esi,[source] - mov ebx,[transtable] - mov edx,[leadskip] - xor eax,eax - } - - /* - ** Skip leading pixels by analyzing the RLE data until the entire - ** requested skip pixel count has been processed. This could result in - ** unprocessed transparent pixels if it ended up in the middle of - ** a transparent pixel run. This is handled in the next block. - */ -moreskip: - __asm { - test edx,edx - jle nomoreskip - dec edx - lodsb - test al,al - jnz moreskip - lodsb - sub edx,eax - inc edx - jmp moreskip - } -nomoreskip: - - /* - ** Handle any left over transparent pixels that would be part of - ** a transparent pixel run that occurs at the end of the leading - ** pixel skip process. - */ - __asm { - neg edx - sub ecx,edx // Account for any left over transparent pixels - lea edi,[edi+edx*2] - } - - /* - ** Output the pixel data to the destination. - */ -moredata: - __asm { - xor eax,eax - or ecx,ecx - jle fini - lodsb - test al,al - jz transparent - mov ax,[ebx+eax*2] - dec ecx - stosw - jmp moredata - } - - /* - ** A transparent pixel run just causes the destination pointer - ** and length count to be adjusted by the length of the run. - */ -transparent: - __asm { - lodsb - lea edi,[edi+eax*2] - sub ecx,eax - jmp moredata - } - -fini:; -} - -#endif - - -#endif diff --git a/code/xsurface.cpp b/code/xsurface.cpp index 9c834b81..b5d28b7f 100644 --- a/code/xsurface.cpp +++ b/code/xsurface.cpp @@ -790,9 +790,6 @@ bool XSurface::Fill_Rect(Rect const & fillrect, int color) } -/// inline asm functions don't have a return statement -#pragma warning(disable: 4035) - /// /// Fills a run of longwords with a color value. /// This is the low level filler that the surface rectangle fill routines use for the bulk @@ -802,27 +799,16 @@ bool XSurface::Fill_Rect(Rect const & fillrect, int color) /// The longword value to store. A 16 bit color must be doubled into /// both halves of it. /// Returns with a pointer to just past the last longword written. -static void *surface_quick_fill(void *buf, int count, int color) +static inline void *surface_quick_fill(void *buf, int count, int color) { - _asm { - push edi /// Bug fixed in TS but not in ShapeSet - - mov ecx, [count] - mov edi, [buf] - - cmp ecx, 0 - jle short fill_end + unsigned int * ptr = (unsigned int *)buf; - mov eax, [color] - rep stosd - - fill_end: - mov eax, edi - pop edi /// Bug fixed in TS but not in ShapeSet + for (int index = 0; index < count; index++) { + *ptr++ = (unsigned int)color; } - // return is in eax + + return(ptr); } -#pragma warning(default: 4035) /*********************************************************************************************** diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 547b0d61..ae15ff35 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,3 +4,4 @@ add_subdirectory(logstress) add_subdirectory(netpacket) add_subdirectory(sosparity) add_subdirectory(spawner) +add_subdirectory(lcwcomp) diff --git a/tests/lcwcomp/CMakeLists.txt b/tests/lcwcomp/CMakeLists.txt new file mode 100644 index 00000000..3190a1c3 --- /dev/null +++ b/tests/lcwcomp/CMakeLists.txt @@ -0,0 +1,30 @@ +# The compressor is compiled straight into the harness. It lives outside code/ so that the +# recursive glob building OpenTS cannot pick this target's entry point up. +add_executable(LcwComp + "${CMAKE_CURRENT_SOURCE_DIR}/lcwcomp.cpp" + "${CMAKE_SOURCE_DIR}/code/lcw.cpp" +) + +target_compile_features(LcwComp PRIVATE cxx_std_20) + +target_include_directories(LcwComp PRIVATE + "${CMAKE_SOURCE_DIR}/code" + "${CMAKE_CURRENT_SOURCE_DIR}" +) + +target_compile_definitions(LcwComp PRIVATE WIN32 _WINDOWS _MBCS) + +# The engine builds this source with SSE2 and precise floating point. The harness matches that +# so a result here carries over to the engine. +target_compile_options(LcwComp PRIVATE + $<$:/MTd /EHsc /Zc:__cplusplus /arch:SSE2 /fp:precise> + $<$:/MT /EHsc /Zc:__cplusplus /arch:SSE2 /fp:precise> +) + +target_link_libraries(LcwComp PRIVATE kernel32 user32 shell32) + +set_target_properties(LcwComp PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" +) + +add_test(NAME lcwcomp COMMAND LcwComp) diff --git a/tests/lcwcomp/lcwcomp.cpp b/tests/lcwcomp/lcwcomp.cpp new file mode 100644 index 00000000..5cc13063 --- /dev/null +++ b/tests/lcwcomp/lcwcomp.cpp @@ -0,0 +1,131 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// Holds LCW_Comp in lcw.cpp to the output the inline assembly it replaced produced. The +// vectors in lcwgolden.h were recorded from that assembly before it was removed. +// +// Compressed blocks are written into save games, so the contract is the exact bytes emitted, +// not merely that they expand again correctly. Both are checked here. Needs no game data. + +#include +#include + +#include "lcw.h" + +#include "lcwgolden.h" + +namespace { + +int const SRCMAX = 65536; +int const DSTMAX = 262144; + +unsigned char Source[SRCMAX]; +unsigned char Dest[DSTMAX]; +unsigned char Roundtrip[SRCMAX * 2]; + +unsigned int Seed = 0; + +int Failures = 0; +int Checked = 0; + + +unsigned int Next_Random(void) +{ + Seed = Seed * 1103515245u + 12345u; + return(Seed >> 8); +} + + +unsigned long long Hash(unsigned char const * data, int size) +{ + unsigned long long hash = 1469598103934665603ULL; + for (int i = 0; i < size; i++) { + hash ^= (unsigned long long)data[i]; + hash *= 1099511628211ULL; + } + return(hash); +} + + +// Must reproduce the generator's inputs exactly or every vector misses. +void Fill_Source(int shape, unsigned int seed, int size) +{ + Seed = seed; + + for (int i = 0; i < size; i++) { + switch (shape) { + case 0: + Source[i] = (unsigned char)(Next_Random() & 0xFF); + break; + + case 1: + Source[i] = 0x7E; + break; + + case 2: + Source[i] = (unsigned char)((i / 17) & 0xFF); + break; + + case 3: + Source[i] = (unsigned char)((Next_Random() % 4) == 0 ? (Next_Random() & 0xFF) : 0x20); + break; + + case 4: + Source[i] = (unsigned char)("OpenTS voxel terrain"[i % 20]); + break; + + default: + Source[i] = (unsigned char)((Next_Random() & 0x03) * 0x40); + break; + } + } +} + +} // namespace + + +int main(void) +{ + for (int i = 0; i < LcwCompGoldenCaseCount; i++) { + LcwCompGoldenCase const & test = LcwCompGoldenCases[i]; + + Fill_Source(test.Shape, test.Seed, test.Size); + std::memset(Dest, 0xA5, sizeof(Dest)); + + int const packed = LCW_Comp(Source, Dest, test.Size); + + if (packed != test.Compressed) { + std::printf("FAILED shape %d size %d: compressed to %d bytes, expected %d\n", + test.Shape, test.Size, packed, test.Compressed); + Failures++; + } else if (Hash(Dest, packed) != test.Hash) { + std::printf("FAILED shape %d size %d: compressed bytes differ from the assembly\n", + test.Shape, test.Size); + Failures++; + } + + // The one byte encoding carries a byte read past the source, so its block holds two + // bytes of data. Bounding the decompress by the source size drops the extra one. + std::memset(Roundtrip, 0, sizeof(Roundtrip)); + int const unpacked = LCW_Uncomp(Dest, Roundtrip, (unsigned long)test.Size); + + if (unpacked != test.Size || std::memcmp(Roundtrip, Source, test.Size) != 0) { + std::printf("FAILED shape %d size %d: round trip returned %d bytes\n", + test.Shape, test.Size, unpacked); + Failures++; + } + + Checked++; + } + + std::printf("%-52s %s\n", "LCW compression matches the recorded assembly", Failures == 0 ? "ok" : "FAILED"); + std::printf("checked %d cases, %d mismatches\n", Checked, Failures); + + return(Failures == 0 ? 0 : 1); +} diff --git a/tests/lcwcomp/lcwgolden.h b/tests/lcwcomp/lcwgolden.h new file mode 100644 index 00000000..1939fd62 --- /dev/null +++ b/tests/lcwcomp/lcwgolden.h @@ -0,0 +1,131 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// Generated from LCW_Comp while it was still inline assembly, and kept so the C++ that +// replaced it can be held to the same output. LCW blocks are written into save games, so +// what matters is the exact bytes emitted, not just that they decompress correctly. +// +// Generated file. Do not hand-edit. + +#pragma once + +struct LcwCompGoldenCase { + int Shape; + unsigned int Seed; + int Size; + int Compressed; + unsigned long long Hash; +}; + +static LcwCompGoldenCase const LcwCompGoldenCases[] = { + {0, 13069u, 1, 9, 1807977557902324117ULL}, + {0, 20988u, 2, 4, 13064096319865035231ULL}, + {0, 28907u, 3, 5, 5915942401490830024ULL}, + {0, 36826u, 7, 9, 8846502117472493464ULL}, + {0, 44745u, 15, 17, 11970627509807455946ULL}, + {0, 52664u, 16, 18, 17546354395159622834ULL}, + {0, 60583u, 17, 19, 18405239772726857934ULL}, + {0, 68502u, 63, 65, 83793028833142472ULL}, + {0, 76421u, 64, 67, 16421560953345050994ULL}, + {0, 84340u, 100, 103, 1776426412059678858ULL}, + {0, 92259u, 255, 261, 14004760160580516757ULL}, + {0, 100178u, 256, 262, 5922497657109482904ULL}, + {0, 108097u, 257, 263, 3500236831736331464ULL}, + {0, 116016u, 1000, 1017, 16469799334716043436ULL}, + {0, 123935u, 4096, 4163, 13679796812110556091ULL}, + {0, 131854u, 16384, 16646, 1129566810827092307ULL}, + {0, 139773u, 65535, 66577, 4701270319259758933ULL}, + {1, 147692u, 1, 4, 15387975727855557662ULL}, + {1, 155611u, 2, 4, 15321972044827107857ULL}, + {1, 163530u, 3, 5, 3752577258656994018ULL}, + {1, 171449u, 7, 5, 17415218352048637521ULL}, + {1, 179368u, 15, 6, 4655718527127227187ULL}, + {1, 187287u, 16, 6, 16183152400164689092ULL}, + {1, 195206u, 17, 6, 9264405149446243413ULL}, + {1, 203125u, 63, 6, 4583733020526306915ULL}, + {1, 211044u, 64, 6, 16111166893563768820ULL}, + {1, 218963u, 100, 9, 8148909191676844567ULL}, + {1, 226882u, 255, 9, 5423557805361288032ULL}, + {1, 234801u, 256, 9, 4987818062494100691ULL}, + {1, 242720u, 257, 9, 7610984057582977262ULL}, + {1, 250639u, 1000, 9, 354769122027670662ULL}, + {1, 258558u, 4096, 9, 10175650356802707306ULL}, + {1, 266477u, 16384, 9, 9740807493131001306ULL}, + {1, 274396u, 65535, 9, 13087234031888934413ULL}, + {2, 282315u, 1, 9, 11843835814251834895ULL}, + {2, 290234u, 2, 4, 17584421726007563793ULL}, + {2, 298153u, 3, 5, 17791319723388371584ULL}, + {2, 306072u, 7, 5, 11160011530942588191ULL}, + {2, 313991u, 15, 6, 10975763781217639721ULL}, + {2, 321910u, 16, 6, 13273826825183582462ULL}, + {2, 329829u, 17, 6, 6355079574465136783ULL}, + {2, 337748u, 63, 21, 2896898578083667180ULL}, + {2, 345667u, 64, 21, 18140551947415754877ULL}, + {2, 353586u, 100, 31, 12191546920540359407ULL}, + {2, 361505u, 255, 76, 16147238993265830839ULL}, + {2, 369424u, 256, 78, 15820006003885581123ULL}, + {2, 377343u, 257, 79, 8805755096697609473ULL}, + {2, 385262u, 1000, 296, 5946853262984892082ULL}, + {2, 393181u, 4096, 1206, 17330344708194719310ULL}, + {2, 401100u, 16384, 1286, 2821454675195587043ULL}, + {2, 409019u, 65535, 1286, 18314874132263956815ULL}, + {3, 416938u, 1, 4, 18141130856368503077ULL}, + {3, 424857u, 2, 4, 16088487775278736849ULL}, + {3, 432776u, 3, 5, 732249865529444488ULL}, + {3, 440695u, 7, 9, 14687256910119648873ULL}, + {3, 448614u, 15, 16, 13038074262944168672ULL}, + {3, 456533u, 16, 15, 3498373380776672657ULL}, + {3, 464452u, 17, 17, 9285944785277013776ULL}, + {3, 472371u, 63, 44, 5643239704878074118ULL}, + {3, 480290u, 64, 48, 3653615617911823110ULL}, + {3, 488209u, 100, 63, 16930939309045484678ULL}, + {3, 496128u, 255, 200, 6295686415325353141ULL}, + {3, 504047u, 256, 172, 4654478162131913837ULL}, + {3, 511966u, 257, 156, 6505142463220528943ULL}, + {3, 519885u, 1000, 642, 8918849814412059247ULL}, + {3, 527804u, 4096, 2356, 7006499902778769132ULL}, + {3, 535723u, 16384, 8800, 13425338241632085147ULL}, + {3, 543642u, 65535, 27705, 6650711024580611343ULL}, + {4, 551561u, 1, 9, 15861568950810363717ULL}, + {4, 559480u, 2, 4, 7823558933484496740ULL}, + {4, 567399u, 3, 5, 16094879465814968578ULL}, + {4, 575318u, 7, 9, 9942793656368200329ULL}, + {4, 583237u, 15, 17, 6415321729831142520ULL}, + {4, 591156u, 16, 18, 17212623010939686725ULL}, + {4, 599075u, 17, 19, 7108732882802460298ULL}, + {4, 606994u, 63, 25, 521383511426542305ULL}, + {4, 614913u, 64, 25, 7440412237121810000ULL}, + {4, 622832u, 100, 27, 15360569510178639014ULL}, + {4, 630751u, 255, 27, 13436011570904534443ULL}, + {4, 638670u, 256, 27, 18005880776047744154ULL}, + {4, 646589u, 257, 27, 473054248371470077ULL}, + {4, 654508u, 1000, 27, 13789505796299680545ULL}, + {4, 662427u, 4096, 27, 15707536257104979397ULL}, + {4, 670346u, 16384, 27, 15779521763705899669ULL}, + {4, 678265u, 65535, 27, 6888907962647354820ULL}, + {5, 686184u, 1, 4, 17630618806569394369ULL}, + {5, 694103u, 2, 4, 11417304975729043793ULL}, + {5, 702022u, 3, 5, 2214001936243117696ULL}, + {5, 709941u, 7, 9, 8907682379062132876ULL}, + {5, 717860u, 15, 15, 4399000346022804956ULL}, + {5, 725779u, 16, 17, 5304175213314670790ULL}, + {5, 733698u, 17, 18, 14683049785485168693ULL}, + {5, 741617u, 63, 56, 15464930974243779964ULL}, + {5, 749536u, 64, 59, 15343719983771361160ULL}, + {5, 757455u, 100, 83, 8663466514268172878ULL}, + {5, 765374u, 255, 176, 18242259449379974659ULL}, + {5, 773293u, 256, 178, 3039109853407777327ULL}, + {5, 781212u, 257, 176, 2516024155533253180ULL}, + {5, 789131u, 1000, 528, 2320462317124866530ULL}, + {5, 797050u, 4096, 541, 11901963684404172949ULL}, + {5, 804969u, 16384, 541, 9981346815892918614ULL}, + {5, 812888u, 65535, 541, 10814168890895549274ULL}, +}; + +static int const LcwCompGoldenCaseCount = 102;