// SwiftShader Software Renderer
//
// Copyright(c) 2005-2013 TransGaming Inc.
//
// All rights reserved. No part of this software may be copied, distributed, transmitted,
// transcribed, stored in a retrieval system, translated into any human or computer
// language by any means, or disclosed to third parties without the explicit written
// agreement of TransGaming Inc. Without such an agreement, no rights or licenses, express
// or implied, including but not limited to any patent rights, are granted to you.
//

#ifndef sw_Surface_hpp
#define sw_Surface_hpp

#include "Color.hpp"
#include "Main/Config.hpp"
#include "Common/Resource.hpp"

namespace sw
{
	class Resource;

	struct Rect
	{
		Rect() {}
		Rect(int x0i, int y0i, int x1i, int y1i) : x0(x0i), y0(y0i), x1(x1i), y1(y1i) {}

		void clip(int minX, int minY, int maxX, int maxY);

		int x0;   // Inclusive
		int y0;   // Inclusive
		int x1;   // Exclusive
		int y1;   // Exclusive
	};

	struct SliceRect : public Rect
	{
		SliceRect() : slice(0) {}
		SliceRect(const Rect& rect) : Rect(rect), slice(0) {}
		SliceRect(const Rect& rect, int s) : Rect(rect), slice(s) {}
		SliceRect(int x0, int y0, int x1, int y1, int s) : Rect(x0, y0, x1, y1), slice(s) {}
		int slice;
	};

	enum Format : unsigned char
	{
		FORMAT_NULL,

		FORMAT_A8,
		FORMAT_R8,
		FORMAT_R3G3B2,
		FORMAT_A8R3G3B2,
		FORMAT_X4R4G4B4,
		FORMAT_A4R4G4B4,
		FORMAT_R5G6B5,
		FORMAT_R8G8B8,
		FORMAT_X8R8G8B8,
		FORMAT_A8R8G8B8,
		FORMAT_X8B8G8R8,
		FORMAT_A8B8G8R8,
		FORMAT_X1R5G5B5,
		FORMAT_A1R5G5B5,
		FORMAT_G8R8,
		FORMAT_G16R16,
		FORMAT_A2R10G10B10,
		FORMAT_A2B10G10R10,
		FORMAT_A16B16G16R16,
		// Paletted formats
		FORMAT_P8,
		FORMAT_A8P8,
		// Compressed formats
		FORMAT_DXT1,
		FORMAT_DXT3,
		FORMAT_DXT5,
		FORMAT_ATI1,
		FORMAT_ATI2,
        FORMAT_ETC1,
		// Floating-point formats
		FORMAT_R16F,
		FORMAT_G16R16F,
		FORMAT_A16B16G16R16F,
		FORMAT_R32F,
		FORMAT_G32R32F,
		FORMAT_A32B32G32R32F, 
		// Bump map formats
		FORMAT_V8U8,
		FORMAT_L6V5U5,
		FORMAT_Q8W8V8U8,
		FORMAT_X8L8V8U8,
		FORMAT_A2W10V10U10,
		FORMAT_V16U16,
		FORMAT_A16W16V16U16,
		FORMAT_Q16W16V16U16,
		// Luminance formats
		FORMAT_L8,
		FORMAT_A4L4,
		FORMAT_L16,
		FORMAT_A8L8,
		// Depth/stencil formats
		FORMAT_D16,
		FORMAT_D32,
		FORMAT_D24X8,
		FORMAT_D24S8,
		FORMAT_D24FS8,
		FORMAT_D32F,                 // Quad layout
		FORMAT_D32F_COMPLEMENTARY,   // Quad layout, 1 - z
		FORMAT_D32F_LOCKABLE,        // Linear layout
		FORMAT_D32FS8_TEXTURE,       // Linear layout, no PCF
		FORMAT_D32FS8_SHADOW,        // Linear layout, PCF
		FORMAT_DF24S8,
		FORMAT_DF16S8,
		FORMAT_INTZ,
		FORMAT_S8,
		// Quad layout framebuffer
		FORMAT_X8G8R8B8Q,
		FORMAT_A8G8R8B8Q,

		FORMAT_LAST = FORMAT_A8G8R8B8Q
	};

	enum Lock
	{
		LOCK_UNLOCKED,
		LOCK_READONLY,
		LOCK_WRITEONLY,
		LOCK_READWRITE,
		LOCK_DISCARD
	};

	class Surface
	{
	private:
		struct Buffer
		{
		public:
			void write(int x, int y, int z, const Color<float> &color);
			void write(int x, int y, const Color<float> &color);
			void write(void *element, const Color<float> &color);
			Color<float> read(int x, int y, int z) const;
			Color<float> read(int x, int y) const;
			Color<float> read(void *element) const;
			Color<float> sample(float x, float y, float z) const;
			Color<float> sample(float x, float y) const;

			void *lockRect(int x, int y, int z, Lock lock);
			void unlockRect();

			void *buffer;
			int width;
			int height;
			int depth;
			int bytes;
			int pitchB;
			int pitchP;
			int sliceB;
			int sliceP;
			Format format;
			Lock lock;
			
			bool dirty;
		};

	public:
		Surface(Resource *texture, int width, int height, int depth, Format format, bool lockable, bool renderTarget);
		
		virtual ~Surface();

		void *lockExternal(int x, int y, int z, Lock lock, Accessor client);
		void unlockExternal();
		inline int getExternalWidth() const;
		inline int getExternalHeight() const;
		inline int getExternalDepth() const;
		inline Format getExternalFormat() const;
		inline int getExternalPitchB() const;
		inline int getExternalPitchP() const;
		inline int getExternalSliceB() const;
		inline int getExternalSliceP() const;

		virtual void *lockInternal(int x, int y, int z, Lock lock, Accessor client);
		virtual void unlockInternal();
		inline int getInternalWidth() const;
		inline int getInternalHeight() const;
		inline int getInternalDepth() const;
		inline Format getInternalFormat() const;
		inline int getInternalPitchB() const;
		inline int getInternalPitchP() const;
		inline int getInternalSliceB() const;
		inline int getInternalSliceP() const;

		void *lockStencil(int front, Accessor client);
		void unlockStencil();
		inline int getStencilPitchB() const;
		inline int getStencilPitchP() const;
		inline int getStencilSliceB() const;

		inline int getMultiSampleCount() const;
		inline int getSuperSampleCount() const;

		void clearColorBuffer(unsigned int colorARGB, unsigned int rgbaMask, int x0, int y0, int width, int height);
		void clearDepthBuffer(float depth, int x0, int y0, int width, int height);
		void clearStencilBuffer(unsigned char stencil, unsigned char mask, int x0, int y0, int width, int height);
		void fill(const Color<float> &color, int x0, int y0, int width, int height);

		Color<float> readExternal(int x, int y, int z) const;
		Color<float> readExternal(int x, int y) const;
		Color<float> sampleExternal(float x, float y, float z) const;
		Color<float> sampleExternal(float x, float y) const;
		void writeExternal(int x, int y, int z, const Color<float> &color);
		void writeExternal(int x, int y, const Color<float> &color);
		
		Color<float> readInternal(int x, int y, int z) const;
		Color<float> readInternal(int x, int y) const;
		Color<float> sampleInternal(float x, float y, float z) const;
		Color<float> sampleInternal(float x, float y) const;
		void writeInternal(int x, int y, int z, const Color<float> &color);
		void writeInternal(int x, int y, const Color<float> &color);

		bool hasStencil() const;
		bool hasDepth() const;
		bool hasPalette() const;
		bool isRenderTarget() const;

		bool hasDirtyMipmaps() const;
		void cleanMipmaps();
		Resource *getResource();

		static int bytes(Format format);
		static int pitchB(int width, Format format, bool target);
		static int pitchP(int width, Format format, bool target);
		static int sliceB(int width, int height, Format format, bool target);
		static int sliceP(int width, int height, Format format, bool target);
		static unsigned int size(int width, int height, int depth, Format format);   // FIXME: slice * depth

		static bool isStencil(Format format);
		static bool isDepth(Format format);
		static bool isPalette(Format format);

		static bool isFloatFormat(Format format);
		static bool isUnsignedComponent(Format format, int component);
		static bool isSRGBreadable(Format format);
		static bool isSRGBwritable(Format format);
		static bool isCompressed(Format format);
		static int componentCount(Format format);

		static void setTexturePalette(unsigned int *palette);

	protected:
		sw::Resource *resource;

	private:
		typedef unsigned char byte;
		typedef unsigned short word;
		typedef unsigned int dword;
		typedef uint64_t qword;

		#if S3TC_SUPPORT
		struct DXT1
		{
			word c0;
			word c1;
			dword lut;
		};

		struct DXT3
		{
			qword a;

			word c0;
			word c1;
			dword lut;
		};

		struct DXT5
		{
			union
			{
				struct
				{
					byte a0;
					byte a1;
				};

				qword alut;   // Skip first 16 bit
			};

			word c0;
			word c1;
			dword clut;
		};
		#endif

		struct ATI2
		{
			union
			{
				struct
				{
					byte y0;
					byte y1;
				};

				qword ylut;   // Skip first 16 bit
			};

			union
			{
				struct
				{
					byte x0;
					byte x1;
				};

				qword xlut;   // Skip first 16 bit
			};
		};

		struct ATI1
		{
			union
			{
				struct
				{
					byte r0;
					byte r1;
				};

				qword rlut;   // Skip first 16 bit
			};
		};

		static void decodeR8G8B8(Buffer &destination, const Buffer &source);
		static void decodeR5G6B5(Buffer &destination, const Buffer &source);
		static void decodeX1R5G5B5(Buffer &destination, const Buffer &source);
		static void decodeA1R5G5B5(Buffer &destination, const Buffer &source);
		static void decodeX4R4G4B4(Buffer &destination, const Buffer &source);
		static void decodeA4R4G4B4(Buffer &destination, const Buffer &source);
		static void decodeP8(Buffer &destination, const Buffer &source);

		#if S3TC_SUPPORT
		static void decodeDXT1(Buffer &internal, const Buffer &external);
		static void decodeDXT3(Buffer &internal, const Buffer &external);
		static void decodeDXT5(Buffer &internal, const Buffer &external);
		#endif
		static void decodeATI1(Buffer &internal, const Buffer &external);
		static void decodeATI2(Buffer &internal, const Buffer &external);
		static void decodeETC1(Buffer &internal, const Buffer &external);

		static void update(Buffer &destination, Buffer &source);
		static void genericUpdate(Buffer &destination, Buffer &source);
		static void *allocateBuffer(int width, int height, int depth, Format format);
		static void memfill4(void *buffer, int pattern, int bytes);

		bool identicalFormats() const;
		Format selectInternalFormat(Format format) const;

		void resolve();

		Buffer external;
		Buffer internal;
		Buffer stencil;

		const bool lockable;
		const bool renderTarget;

		bool dirtyMipmaps;
		unsigned int paletteUsed;

		static unsigned int *palette;   // FIXME: Not multi-device safe
		static unsigned int paletteID;

		bool hasParent;
	};
}

#undef min
#undef max

namespace sw
{
	int Surface::getExternalWidth() const
	{
		return external.width;
	}

	int Surface::getExternalHeight() const
	{
		return external.height;
	}

	int Surface::getExternalDepth() const
	{
		return external.depth;
	}

	Format Surface::getExternalFormat() const
	{
		return external.format;
	}

	int Surface::getExternalPitchB() const
	{
		return external.pitchB;
	}

	int Surface::getExternalPitchP() const
	{
		return external.pitchP;
	}

	int Surface::getExternalSliceB() const
	{
		return external.sliceB;
	}

	int Surface::getExternalSliceP() const
	{
		return external.sliceP;
	}

	int Surface::getInternalWidth() const
	{
		return internal.width;
	}

	int Surface::getInternalHeight() const
	{
		return internal.height;
	}

	int Surface::getInternalDepth() const
	{
		return internal.depth;
	}

	Format Surface::getInternalFormat() const
	{
		return internal.format;
	}

	int Surface::getInternalPitchB() const
	{
		return internal.pitchB;
	}

	int Surface::getInternalPitchP() const
	{
		return internal.pitchP;
	}

	int Surface::getInternalSliceB() const
	{
		return internal.sliceB;
	}

	int Surface::getInternalSliceP() const
	{
		return internal.sliceP;
	}

	int Surface::getStencilPitchB() const
	{
		return stencil.pitchB;
	}

	int Surface::getStencilPitchP() const
	{
		return stencil.pitchP;
	}

	int Surface::getStencilSliceB() const
	{
		return stencil.sliceB;
	}

	int Surface::getMultiSampleCount() const
	{
		return sw::min(internal.depth, 4);
	}

	int Surface::getSuperSampleCount() const
	{
		return internal.depth > 4 ? internal.depth / 4 : 1;
	}
}

#endif   // sw_Surface_hpp
