//================================================================
// E3D
//
// Core Rendering-related features
//
// AUTHOR:  Gabor Nagy
// DATE:    2026-Jul-22 23:03:07
//
// Copyright (C) 1996-2026 by Gabor Nagy. All rights reserved.
//================================================================
#ifndef _E3D_Rendering_h
#define _E3D_Rendering_h

#include <assert.h>
#include <unistd.h>
#include <stdint.h>    // For uint32_t etc. for E_Random

#ifndef _EClass_h
#include <EClass.h>
#endif

#ifndef _EThreads_h
#include <EThreads.h>
#endif

#ifndef _E3D_Node_h
#include <E3D/Node.h>
#endif

#ifndef _E3D_Camera_h
#include <E3D/Camera.h>
#endif

#ifndef _E3D_Window_h
#include <E3D/UI/Window.h>
#endif

#ifndef _E3D_KDTree_h
#include <E3D/Accelerators/KDTree.h>
#endif

#include <Image/GL/EpGL.h>

#if defined (__cplusplus) && (!(defined (__CPLUSLIBS)))
extern "C" {
#endif


#define E3dRenderDEBUG  1

#define E3dLIGHTS_MULTIPLIED_BY_NDOTL  0  // If not 0, Shader->ShadeSurface() methods expect light Power values to be already multiplied by Ndot:

// Default SSAA subpixel count (4x means 2x resolution off-screen buffer in X and Y directions)
#ifdef EpGL_HAS_FBO
#define E3d_DefaultSSAANSamples  4
#else
#define E3d_DefaultSSAANSamples  1
#endif


#define _E3dM_Modulate3(pColor, pFactor, pChannelMask, pClampFunc) do\
{\
	float*  lColor = (float*)(pColor);\
	switch(pChannelMask)\
	{\
		case EpRED:\
			{\
				const float  lV = pFactor * LTextureColor.R;\
				lColor[0] += lV;\
				lColor[1] += lV;\
				lColor[2] += lV;\
\
				pClampFunc\
			}\
		break;\
\
		case EpGREEN:\
			{\
				const float  lV = pFactor * LTextureColor.G;\
				lColor[0] += lV;\
				lColor[1] += lV;\
				lColor[2] += lV;\
\
				pClampFunc\
			}\
		break;\
\
		case EpBLUE:\
			{\
				const float  lV = pFactor * LTextureColor.B;\
				lColor[0] += lV;\
				lColor[1] += lV;\
				lColor[2] += lV;\
\
				pClampFunc\
			}\
		break;\
\
		case EpALPHA:\
			{\
				const float  lV = pFactor * LTextureColor.A;\
				lColor[0] += lV;\
				lColor[1] += lV;\
				lColor[2] += lV;\
\
				pClampFunc\
			}\
		break;\
\
		case EpRED | EpGREEN | EpBLUE:\
			{\
				const float  lV = pFactor;\
				lColor[0] += LTextureColor.R * lV;\
				lColor[1] += LTextureColor.G * lV;\
				lColor[2] += LTextureColor.B * lV;\
\
				pClampFunc\
			}\
		break;\
\
		case EpRED | EpGREEN | EpBLUE | EpALPHA:\
			{\
				const float  lV = pFactor * LTextureColor.A;\
				lColor[0] += LTextureColor.R * lV;\
				lColor[1] += LTextureColor.G * lV;\
				lColor[2] += LTextureColor.B * lV;\
\
				pClampFunc\
			}\
		break;\
\
		case EpINTENSITY:\
			{\
				const float  lV = pFactor * (LTextureColor.R + LTextureColor.G + LTextureColor.B) * 0.333333;\
				lColor[0] += lV;\
				lColor[1] += lV;\
				lColor[2] += lV;\
\
				pClampFunc\
			}\
		break;\
	}\
} while(0)


//================================================================================================
// Add Emission (direct radiance) of area light, when directly looking into an area light.
//
// Emission colors are in radiance (not total power of light).
// Also, luckily, we only need to compute radiant exitance (radiosity) because:
// - the cosine term due to the viewing angle is cancelled out by looking at a larger area at lower angles.
// - the distance inverse-square term (due to the solid angle) is cancelled out by the same solid angle looking at a larger area of the area light from farther away.
//================================================================================================
static inline void _E3d_AddEmission(const E3dSurfacePoint* PPoint, const E3dMaterial* PMaterial, const float* PEmission,
 const int PSpectralIndex, const int PNSpectralSamples,  float* PColor)
{
	const E3dMatLight*  LMatLight = &PMaterial->Light;const E3dCoordinate  LCutoffAngle = LMatLight->CutoffAngle;
	if(LCutoffAngle == 0.0)
	{
		E3dM_SpectralLoop(PSpectralIndex, PNSpectralSamples) PColor[LSI] += PEmission[LSI];
	}
	else if(E_Dot3_vv(PPoint->Normal, PPoint->PointToViewer) > LMatLight->CutoffAngleCos)
	{
		E3dM_SpectralLoop(PSpectralIndex, PNSpectralSamples) PColor[LSI] += PEmission[LSI] * 90.0 / LCutoffAngle;
	}
}


//================================================
// Beer-Lambert law of absorption
//================================================
static inline void _E3d_ExtinctionN(const EIndex PSpectralIndex, const EIndex PNSpectralSamples,  const float* PExctinction, const E3dCoordinate PDistance,  float* PColor)
{
	{
		E3dM_SpectralLoop(PSpectralIndex, PNSpectralSamples)  PColor[LSI] *= E3d_Exp(-PDistance * PExctinction[LSI]);
	}
}


//================================================
// Beer-Lambert law of absorption
//================================================
static inline void _E3d_Extinction(const EIndex PSpectralIndex, const EIndex PNSpectralSamples,  const E3dShader* PVolumeShader, const E3dCoordinate PDistance, float* PColor)
{
	if(PVolumeShader && PVolumeShader->HasVolumeAbsorption)
	{
		const float*  LAbsorption = PVolumeShader->Volume.Absorption.Samples;const float*  LScattering = PVolumeShader->Volume.Scattering.Samples;
		E3dM_SpectralLoop(PSpectralIndex, PNSpectralSamples)  PColor[LSI] *= E3d_Exp(-PDistance * (LAbsorption[LSI] + LScattering[LSI]));

//		const float*  LExtinction = PVolumeShader->Volume.Extinction.Samples;
//		E3dM_SpectralLoop(PSpectralIndex, PNSpectralSamples)  PColor[LSI] *= E3d_Exp(-PDistance * LExtinction[LSI]);
	}
}


#define _E3d_MIPMap(pRay, pRTriangle, pIndex) do\
{\
	LMipRay.Direction = pRay->PixelCornerDirections[pIndex];\
	const E3dCoordinate	 lT = E3dRay_IntersectTriangle_KnownnHit_UV(&LMipRay, pRTriangle, &LUVs[pIndex]);\
\
	E3dAnisoPoint*  lAnisoPoint = LAnisoPoints + pIndex;\
	E3dCoord3*      lPosition = &lAnisoPoint->Position;\
/* Set hit position, for procedural texture-mappers	*/\
	E_AddScaled3_vvp(LMipRay.Origin, LMipRay.Direction, lT,  lPosition);\
} while(0)


//==========================================================================================
// Normalize texture coordinates (convert to 0.0...1.0 range) by applying tiling etc.
//==========================================================================================
#define _M_TexcoordsNormalize(pMapper) do\
{\
	const E3dCoordinate  lSRepeatsF = (E3dCoordinate)(pMapper->SCount), lTRepeatsF = (E3dCoordinate)(pMapper->TCount);\
\
	if((pMapper->Class == E3dTextureMapperFreehand_class) || (LS >= 0.0f && LT >= 0.0f && LS <= lSRepeatsF && LT <= lTRepeatsF))\
/*	if(0)*/\
	{\
printf("%s() %s:%d  _M_TexcoordsNormalize()\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);\
		if(LS >= 0.0) LS -= E3d_Floor(LS);  /* Modulo...	*/\
		else LS += (1.0 - E3d_Ceil(LS));\
\
		if(LT >= 0.0) LT -= E3d_Floor(LT);  /* Modulo...	*/\
		else LT += (1.0 - E3d_Ceil(LT));\
	}\
	else LS = E3dST_OFF_TEXTURE;\
} while(0)


#define E3d_Span4_vv(pS, pField, pD) do\
{\
	E3dCoordinate  lSpan;\
\
	pD.pField = E3d_Abs(pS[0].pField - pS[1].pField);\
	lSpan = E3d_Abs(pS[0].pField - pS[3].pField);if(pD.pField < lSpan) pD.pField = lSpan;\
	lSpan = E3d_Abs(pS[0].pField - pS[2].pField);if(pD.pField < lSpan) pD.pField = lSpan;\
	lSpan = E3d_Abs(pS[1].pField - pS[2].pField);if(pD.pField < lSpan) pD.pField = lSpan;\
	lSpan = E3d_Abs(pS[1].pField - pS[3].pField);if(pD.pField < lSpan) pD.pField = lSpan;\
	lSpan = E3d_Abs(pS[2].pField - pS[3].pField);if(pD.pField < lSpan) pD.pField = lSpan;\
} while(0)


// Temporary solution to RendererClass re-activation...
// We'll always allocate this size, regardless of StructSize, so if we need to deactivate/reactivate
// a Class and its Renderers and the new StructSize is greater, we don't need to go throught all the
// references of Renderers.
// Eventually we'll do this though
//
#define E3dRENDERER_MAX_SIZE  262144


#define E3dAE_OFF            0x00
#define E3dAE_SPOT_METERING  0x01


// Anti-aliasing
//
enum
{
	E3dAA_NONE = 0,
	E3dAA_02x,
	E3dAA_04x,
	E3dAA_06x,
	E3dAA_09x,
	E3dAA_02xR,
	E3dAA_03xR,
	E3dAA_04xR,
	E3dAA_05xR,
	E3dAA_07xR,
	E3dAA_09xR,
	E3dAA_16xR,
	E3dAA_25xR,
	E3dAA_36xR,
	E3dAA_49xR,
	E3dAA_64xR,
	E3dAA_ADAPTIVE_2x2,
	E3dAA_ADAPTIVE_3x3,
	E3dAA_ADAPTIVE_4x4
};


enum
{
	E3dUNIFORM = 1,
	E3dSTRATIFIED
};


#ifndef _E3dScene
#define _E3dScene
typedef struct E3dScene_S  E3dScene;
#endif


#ifndef _E3dRenderer
#define _E3dRenderer
typedef struct E3dRenderer_S         E3dRenderer;
typedef struct E3dRenderer_thread_S  E3dRenderer_thread;
#endif

#ifndef _E3dRayTracer
#define _E3dRayTracer
typedef struct E3dRayTracer_S         E3dRayTracer;
typedef struct E3dRayTracer_thread_S  E3dRayTracer_thread;
#endif

typedef struct E3dRendererSet_S  E3dRendererSet;


enum
{
	E3dGET = 1,
	E3dSET
};

enum
{
	E3dSHADER_ID = 1,
	E3dVERTEX_PROGRAM_RESOURCES,
	E3dVERTEX_PROGRAM_SOURCE,
	E3dFRAGMENT_PROGRAM_RESOURCES,
	E3dFRAGMENT_PROGRAM_SOURCE,
};



#define E3dPhotonCore\
	E3dKDTreePointCore;\
	char            Phi, Theta  /* quantized angle  */

typedef struct
{
	E3dPhotonCore;
} E3dCorePhoton;


typedef struct
{
	E3dPhotonCore;

	float  PowerRGB[3];
} E3dRGBPhoton;

typedef struct
{
	E3dPhotonCore;

	float           Power;
	unsigned short  SpectralIndex;
} E3dSpectralPhoton;


typedef struct
{
	E3dShadowMap*  ShadowMap;
	E3dCoordinate  LODSizeFactor;
	E3dMatrix      LightViewMatrix, LightViewMatrixInv,
	               LightFrustumMatrix, LightFrustumMatrixInv,
	               ShadowProjectionMatrix;         // World-space shadow Matrix
	EBool          Valid;
} E3dShadowMapT;


// E3dLights can be instantiated from multiple Models. This struct is used to store unique instances with spectrally sampled colors.
// Position, Direction etc. are cached here vs. accessing the Model and Light, so Renderers can run independently without worrying
// about Models and Lights changing in the Scene.
//
typedef struct 
{
	E3dCoord3      Position, Direction;                   // Position and direction (for spotlights) in world-space

	int            Type;                                  // Light source type
	EcRGBf32Color  RGBColor;
	float          Power[EcMAX_NUM_OF_SPECTRAL_SAMPLES];  // Spectral color of the light source
	float          DiffuseFactor, SpecularFactor;
	float          SpotAngle, SpotPenumbraAngle, SpotCutoffAngleCos, SpotExponent,
	               AttenuationConstant,
	               AttenuationLinear,
	               AttenuationQuadratic,
	               ShadowBrightness;       // If a point is in shadow from this light, its diffuse color will be multiplied by this value

	E3dShadowMapT  ShadowMap;
} E3dRLightInstance;
EListA_t(E3dRLightInstance);


// Renderers
//
typedef void   (*E3dRendererStatsUpdateFunc)(E3dRenderer* PRenderer);
typedef int    (*E3dRenderFunc)(E3dWindow* P3DWindow, E3dRenderer* PRenderer, E3dScene* PScene,  E3dCamera* PCamera, E3dModel* PCameraModel,
                E3dRendererStatsUpdateFunc PStatsUpdateFunc);

typedef int    (*E3dRendererInfoFunc)(E3dRenderer* PRenderer,  E3dModel* PModel, E3dGeometry* PGeometry, EObject* PShadeable,  int, ...);
typedef EBool  (*E3dRendererUpdateFunc)(E3dRenderer*, E3dRenderer*);

typedef int    (*E3dTraceRayFunc)(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, E3dStackRay* PRay, int PRayIter, E3dShaderResult* PResult);

typedef void   (*E3dShadePointFunc)(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, const E3dRTriangle* PRTriangle, E3dSurfacePoint* PPoint,
                E3dStackRay* PRay,  const E3dMaterial* PSurfaceMaterial, const E3dShader* PVolumeShader, const E3dShader* PShaderBeyondSurface,
                E3dShaderResult* PResultTmp, E3dShaderResult* PResult);

typedef void*  (*E3dBindMaterialFunc)(E3dMaterial* PMaterial, E3dRenderer* PRenderer, E3dGPUContext* PGPUContext,  E3dScene* PScene, E3dModel* PModel,
                E3dGeometry* PGeometry, EObject* PShadeable,  const E3dRLightInstance* PLightInstances, const EIndex PNLightInstances);

typedef void   (*E3dUnbindMaterialFunc)(E3dMaterial* PMaterial, void* PHwShader,  E3dRenderer* PRenderer, E3dGPUContext* PGPUContext);

typedef struct
{
	E3dCamera*    Camera;               // Currently used Camera. This is a COPY (not a reference) of the active Camera, to make sure
	                                    // that it does not change while a frame is being rendered, even in interactive/multi-threaded mode.
	E3dModel*     CameraModel;          // The Model that instantiates the Camera used - COPY

	EChangeFlags  SceneChangeFlags;     // What changed in the Scene
	EChangeFlags  RendererChangeFlags;  // What changed in this Renderer
} E3dChanges;

typedef struct E3dShaderDevice_S    E3dShaderDevice;
typedef struct E3dRendererClass_S   E3dRendererClass;
typedef struct E3dRendererClass_S*  E3dRendererClassPtr;
EList_t(E3dRendererClassPtr);


typedef void  (*E3dShaderClassRendererFunc)(E3dShaderClass* PShaderClass, E3dShaderClassRendererBinding* PShaderClassRendererBinding);

typedef uint32_t  E3dRendererFeatureFlags;

#define E3dRendererClassCore\
	EBaseClassCore(E3dRenderer);\
\
	void				(*ThreadInit)(const E3dRenderer* PRenderer, E3dRenderer_thread* PThread, const EBool PNew);  /* RendererThread constructor / resetter */\
	void				(*ThreadFree)(E3dRenderer_thread* PThread, const E3dChanges* PChanges);                      /* RendererThread destructor */\
	void				(*FreeCaches)(E3dRenderer* PRenderer);                                                       /* Free cached data (accelerators, triangles etc.) */\
\
	E3dRendererUpdateFunc       Update;             /* Called when Resources change                              */\
	E3dRenderFunc               Render;\
	E3dBindMaterialFunc         BindMaterial;\
	E3dUnbindMaterialFunc       UnbindMaterial;\
	ECoreObjectGUIFunc          GUI;                /* Update/free GUI panel for renderer instance               */\
	E3dShaderClassRendererFunc  ShaderClassFree;    /* Called when a ShaderClass is freed                                       */\
\
	E3dRendererInfoFunc         Info;               /* Get source code of a shader for this renderer class etc.  */\
	unsigned int                ShaderClassRendererBindingSize;\
	unsigned int                ShaderVariantSize;  /* Size of structure representing a shader variant for this renderer class  */\
	E3dShaderDevice*            ShaderDevice

struct E3dRendererClass_S
{
	E3dRendererClassCore;
};


// Display flags for 3DWindows
// Display-mode flags
//
#define E3dDF_NONE                  0x00000000
#define E3dDF_WIREFRAME_UNSELECTED  0x00000001  // Draw Edges of unselected objects
#define E3dDF_WIREFRAME_SELECTED    0x00000002  // Draw Edges of selected objects
#define E3dDF_SOLID                 0x00000010  // Draw surfaces
#define E3dDF_Z_BUFFER              0x00000100  // Use Z-buffer

#define E3dDF_COLOR                 0x00000400  // Color surfaces flat
#define E3dDF_LIGHTING              0x00000800  // Use lighting
#define E3dDF_TEXTURES              0x00001000
#define E3dDF_TRANSPARENCY          0x00002000

#define E3dDF_CONTROLS              0x00004000  // Drawing controls (we are after tone-mapping, so call GLDrawControls)

#define E3dDF_MODEMASK              0x00007FFF



// Display modes for type-selective window redrawing and picking
//
#define E3dDM_Wireframe    (E3dDF_WIREFRAME_UNSELECTED | E3dDF_WIREFRAME_SELECTED)
#define E3dDM_WireAndZ     (E3dDM_Wireframe | E3dDF_Z_BUFFER)
#define E3dDM_ColorWire    (E3dDF_WIREFRAME_SELECTED | E3dDF_Z_BUFFER | E3dDF_COLOR | E3dDF_Z_BUFFER)
#define E3dDM_ShadedWire   (E3dDF_WIREFRAME_SELECTED | E3dDF_Z_BUFFER | E3dDF_LIGHTING | E3dDF_Z_BUFFER)
#define E3dDM_Solid        (E3dDF_SOLID | E3dDF_Z_BUFFER)
#define E3dDM_ColorSolid   (E3dDF_SOLID | E3dDF_Z_BUFFER | E3dDF_COLOR)
#define E3dDM_ShadedSolid  (E3dDF_SOLID | E3dDF_Z_BUFFER | E3dDF_LIGHTING)


// Display flags for 3D windows
// Node flags
//
#define E3dDF_SELECTED   0x01000000
#define E3dDF_LOCKED     0x02000000
#define E3dDF_PICK       0x04000000

#define E3dDF_NODEFLAGS  0x07000000


#define E3dDF_ALL  (E3dDF_MODEMASK|E3dDF_NODEFLAGS)



enum
{
	E3dAreaLightsNONE = 0,
	E3dAreaLightsHEMISPHERE,
	E3dAreaLightsSAMPLE_AT_VTX,
	E3dAreaLightsSAMPLE,
	E3dAreaLightsSAMPLE_FOR_PHOTONS
};


enum
{
	E3dSampleRANDOM = 1,
	E3dSampleHALTON
};


typedef struct
{
	EInt32_2	Size;
	EInt32_2	RegionStart, RegionSize;
} E3dImageSpecs;




#define E3dLOD_MULT	(1.0 / 200.0)

// Renderer states
//
enum
{
	E3dIDLE = 0,
	E3dPREPARING_FRAME,
	E3dRENDERING,
	E3dPAUSED,    // Incomplete frame, Renderer stopped, but able to resume
	E3dSTOPPED    // Incomplete frame, Renderer stopped, not able to resume (but may not have to rebuild acceleration structures etc.)
};


typedef struct
{
	E3dCoord3  Position;
} E3dAnisoPoint;

typedef struct
{
	E3dCoord2      UVs[E3dMAX_TEXTURE_ANISOTROPY];
	E3dAnisoPoint  Points[E3dMAX_TEXTURE_ANISOTROPY];
	E3dST          STs[E3dMAX_TEXTURE_ANISOTROPY];
} E3dAniso;


enum
{
	E3dRADIANCE = 1,
	E3dIRRADIANCE_DIFFUSE,
	E3dIRRADIANCE_SHI,     // Spherical harmonics irradiance map
	E3dIRRADIANCE_SHR,     // Spherical harmonics radiance map / light field
	E3dIRRADIANCE_SHO,     // Spherical harmonics occlusion map
	E3dIRRADIANCE_WAVELET
};


typedef struct
{
	int      NumOfSubFrames;    // Render this many sub-frames and average them, e.g. for motion blur
	uint8_t  StartPixelSize;    // 2, 4, 8, 16 etc. for progressive, pixelized rendering
	uint8_t  PixelMeaning;      // E3dRADIANCE (normal rendering), E3dIRRADIANCE_DIFFUSE, E3dIRRADIANCE_SHI etc.
} E3dRendererSettables;



typedef struct
{
	float     Radius, Power, Strength;  // User-settable values

	float     Work_Power;               // Power that matches the kernel (kernel needs to be rebuilt if this changes)
	float     Work_Strength;            // Strength that matches the kernel (kernel needs to be rebuilt if this changes)
#ifdef USEOpenGL
	GLuint    GLKernelTexture;
	EInt32_2  GLFBOSize;
#endif
} E3dBloom;

extern EArg  E3dBloomUI_Radius[];
extern EArg  E3dBloomUI_Strength[];
extern EArg  E3dBloomUI_Power[];


#define E3dMAX_REFINEMENT_PASSES  8   // Supports starting rendering from 256x256 macro-pixels


//--------------------------------
// Lightmap rendering
//--------------------------------
typedef struct
{
	E3dTextureMapper*  TextureMapper;  /* Current texture-mapper and its:   */\
	E3dModel*          Model;          /* ... Model, ...                    */\
	E3dGeometry*       Geometry;       /* ... Geometry and ...              */\
	EObject*           Shadeable;      /* ... Shadeable (e.g. PolyGroup) for rendering light-maps  */\
} E3dLightMap;


#define E3dDrawNODE           0x01
#define E3dDrawNODE_CUSTOM    0x02
#define E3dDrawNODE_CONTROLS  0x04

typedef struct
{
	E3dNode*           Node;
	E3dNodeComponent*  NodeComponent;
	uint32_t           DrawFlags;      // E3dDrawNODE etc.
} E3dDrawItem;


// E.g. last stage: Items to draw after others, possibly in a sorted order (e.g. transparent objects, overdraw skeletons with no Z test,  etc.). Lazily grown array of Items
typedef struct
{
	EObjectCore(ECore);
	EListA(, E3dDrawItem, DrawItems);
} E3dDrawingStage;

extern EObjectClass  E3dDrawingStage_class[];

extern const char  E3dDrawingStage_Transparent[];
extern const char  E3dDrawingStage_Last[];


typedef struct
{
	uint16_t  MSAA;                 // Number of MSAA samples

// Supersampling / full-screen antialiasing (render at higher res, filter down), offscreen render target resolution subpixels. E.g. 4 means 2x resolution in both directions
	uint16_t  SSAA;

	EBool     LineAA, PolygonAA;    // Traditional, blending-based line and polygon AA (e.g. on SGI)
} E3dGPUAA;


typedef struct
{
	uint32_t   Textures:1, HDRImage:1, ZBuffer:1, SolidSurfaces:1, FlatColor:1, Lighting:1, ProgressiveRefinement:1;
} E3dRendererFeatures;

#define E3dRendererCore\
	EObjectCore(E3dRenderer);\
\
	E3dCamera*  Camera;       /* Currently used Camera. This must be a COPY, not a reference to an  */\
	                          /* active Camera to make sure that it does not change while a frame   */\
	                          /* is being rendered, even in interactive/multi-threaded mode.        */\
	E3dModel*   CameraModel;  /* The Model that instantiates the Camera used                        */\
\
\
/* "User" contains fields settable by the user. Those are copied over to "Work" when a frame starts. This is used for settings that could not be safely changed */\
/* during rendering, and those that we want to reset after the frame started (e.g. we don't want "PixelMeaning" to stick when rendering irradiance maps). */\
	E3dRendererSettables  User, Work;\
\
	uint16_t  ColorModel;                    /* EpRGB or EpSPECTRAL             */\
	uint16_t  NumOfSpectralSamples;          /* E.g. 3 for RGB                  */\
	float     WavelengthMin, WavelengthMax;  /* Wavelength range in nanometers  */\
	float*    SpectralToR;                   /* RGB weights                     */\
	float*    SpectralToG;\
	float*    SpectralToB;\
	float     RGBtoXYZ[16];                  /* RGB to CIE XYZ Matrix    */\
	float     XYZtoRGB[16];                  /* CIE XYZ to RGB Matrix    */\
\
	E3dMaterial*  CameraIsInThisMaterial;    /* The Camera is inside this Material. Needed for volume effects */\
/* Point / spot etc. lights that are visible and have a non-black color */\
\
	EBool     UsePointLights;\
	EListA_s(, E3dRLightInstance, VisibleLights);\
\
	EIndex       FrameCnt;            /* Frames rendered since this Renderer was created.        */\
	EIndex       WorkingFrameCnt;     /* Frame currently being rendered (FrameCnt may be ahead)  */\
\
	E3dRendererSet*    RendererSet;\
	E3dRendererClass*  PreVizRendererClass;  /* E.g. wireframe to see the outline for long renders  */\
\
\
	E3dLightMap      LightMap;\
\
/* Image settings */\
\
	E3dImageSpecs    ImageSpecs;            /* Copied from the RendererSet when starting a render, so changes won't interfere with a running Renderer */\
\
	uint8_t          AntiAliasingType;      /* 0, E3dUNIFORM, E3dSTRATIFIED     */\
	int              NAnisotropicTextureSamples;  /* For anisotropic texture sampling  */\
\
	E3dBloom         Bloom;\
\
	EpToneMapper     HDRToneMapper;\
	EpExposure       Exposure;\
	int              ExposureMode;\
	EBool            KeepHDRImage;                 /* Create High Dynamic Range image  */\
	EBool            KeepHyperSpectralImage;       /* Keep hyperspectral PixelBiffer in Image0 / Image1, so it can be saved (e.g. to a TIFF file) */\
	EBool            SaveDepthImage;\
	EBool            SaveShadowImage;\
\
	double           SizeFactor;                   /* atan(FieldOfView / 2) * WindowSizeY, for LODs    */\
\
	EcRGBAf32Color   BackgroundRGBAfColor;\
\
	float            AmbientLightColor[EcMAX_NUM_OF_SPECTRAL_SAMPLES];\
\
\
	EIndex           NumOfExtraShaderLights;       /* Other than AreaLight samples       */\
\
	EListA_s(, E3dRPolyGroupPtr, RPolyGroups);     /* Pre-transformed RPolyGroups for rendering */\
\
/* This lookup table allows us to only duplicate each Material once, for rendering (rather than for every Shadeable. We identify Materials by their ID field. */\
/* It also allows for optimizing Material updates in the Renderer. */\
	EListA_s(, E3dMaterialPtr, Materials);\
\
	EList(, E3dAreaLight*, AreaLights);\
	EList(, E3dRTriangle*, LightTriangles);\
\
	uint8_t          AreaLightType;\
\
	EBool            TexturesOn, ShadowsOn, ReflectionsOn, TransparencyOn, FogOn;\
\
\
/* Internal variables */\
\
	E3dRendererFeatures  Features;\
\
	E3dDisplayFlags      HiddenObjectDisplayFlags;\
\
	EIndex2*         PixelRemappingGrid;             /* Image->Size.X * Size.Y grid with pseudo-randomly remapped pixel coordinates for frameless rendering  */\
	EIndex2*         PixelRemappingGridProgressive;  /* Image->Size.X * Size.Y grid with pseudo-randomly remapped pixel coordinates for frameless rendering  */\
\
	EpImage*         Image0;          /* Double buffering : the two buffers                         */\
	EpImage*         Image1;\
\
	EpImage*         ImageRend;       /* Double buffering : the image being rendered (back-buffer)  */\
	EpImage*         ImageDraw;       /* Double buffering : the image being shown (front-buffer)    */\
\
	EpImage*         AccumImage;      /* Temporal sub-frames are accumulated into this Image        */\
\
	EpImage*         ShadowImage;\
	EpImage*         ShadowAccumImage;\
\
	EpImage*         DepthImage;\
\
	E3dChanges       CurrentChanges;      /* Changes currently being processed    */\
/*\
Render 'queue' for requesting changes while the Renderer is busy, or when it's inactive.\
E.g. the Ray-tracer in a 3DWindow's RendererSet is inactive when the current Renderer is GLSL, but Scene, Camera etc. changes should be marked here,\
so they will take effect if the user switches to the Ray-tracer\
*/\
	E3dChanges       RequestedChanges;\
\
	EIndex               NumOfPixelsComputed, NumOfPixelsDrawn;\
\
	E3dOverlaySettings   Overlay;\
	E3dShow              Show;\
\
/* Internal variables */\
	uint32_t     State : 4,                /* E3dIDLE, E3dPREPARING_FRAME, E3dRENDERING, E3dPAUSED, E3dSTOPPED */\
	             BuildingAccelerator : 1,\
	             GoRender : 1, FirstIteration : 1, ShowProgress : 1,\
	             GoSequenceRender : 1;\
	ETime        StartMilliseconds, FinishMilliseconds,\
	             LastRedrawMilliseconds, LastStatsUpdateMilliseconds;\
	ETime        PrevFrameMicroSeconds, FrameTimeMicroseconds, LastDisplayedFrameTimeMicroseconds;\
\
	ETime        Start_TimeStamp;      /* The last time a frame started. Used to detect whether a change should trigger a re-render. */\
	int          SubFrame;             /* Current sub-frame        */\
	EIndex       NumOfShaderLights;\
\
	EListA(, E3dDrawingStage*, DrawingStages);  /* For example, "Opaque", "Transparent", "Last".  */\
\
/* Multi-threading */\
\
	EThreadSet       ThreadSet;\
	int              NumOfThreadLocks;\
	pthread_mutex_t  Workload_mutex, Progress_mutex, Exposure_mutex,\
	                 Debug_mutex;      /* Used for printfs etc. */\
\
	EIndex           CurrentPixelSizeR;\
	EIndex           NewPixelsPerPass[E3dMAX_REFINEMENT_PASSES];int  Pass;\
	int              AASubFrame;       /* Current anti-aliasing sub-frame            */\
	EIndex2          ImageSizeP;       /* Macro pixels, depends on CurrentPixelSizeR */\
	EIndex           NextPixel, PixelsPerPacket, EndPixel;\
	EBool            AreaLightsEnabled

struct E3dRenderer_S
{
	E3dRendererCore;
};



#define E3dGPURendererCore\
	E3dRendererCore;\
	E3dGPUAA  AntiAliasing

typedef struct
{
	E3dGPURendererCore;
} E3dGPURenderer;

extern E3dRendererClass  E3dGPURenderer_class[];



//#define E3dRENDERER_THREAD_MAX_STATESTACK_SIZE  16

#ifdef E3dRENDERER_THREAD_MAX_STATESTACK_SIZE
	#define E3dRendererThreadStateStack\
		char  StateStr[E3dRENDERER_THREAD_MAX_STATESTACK_SIZE][1024];\
		int   StatePtr
#else
	#define E3dRendererThreadStateStack
#endif  // E3dRENDERER_THREAD_MAX_STATESTACK_SIZE


//
// Data writable by individual rendering threads
//
#define E3dRenderer_thread_core\
	EThreadCore;\
\
	E3dWindow*       L3DWindow;\
	E3dRenderer*     Renderer;\
	E3dScene*        Scene;\
	E3dCamera*       Camera;E3dModel*  CameraModel;\
	E3dTraceRayFunc  TraceRayFunc;\
	E3dRendererThreadStateStack;\
\
	E3dLightMap      LightMap;\
\
	E3dCoord2        StratifiedUV;                             /* Anti-aliased renderers pass down stratified 0...1 U/V values  */\
/* For MIP-mapping / anisotropic texture filtering	*/\
	E3dAniso          Aniso;\
\
	E3dShaderLight*  ShaderLights;EIndex  NumOfShaderLights;  /* During rendering, we collect lights here that affect shaders (visible from a given point)  */\
	EIndex           NumOfLightSamplesFacingThisWay,          /* These are used for determining light occlusion ratio    */\
	                 NumOfUnshadowedLightSamples;\
	ERandomState     RandomState[5];\
	ERandomState     LightSamplingRandomState[5]



struct E3dRenderer_thread_S
{
	E3dRenderer_thread_core;
};



// RendererSets group varous Renderer instances (of different Classes) per view port (3DWindow)
//
struct E3dRendererSet_S
{
	EObjectCore(EObject);

	char*          FileName;            // For saving image(s)/animations
	double         StartFrame, EndFrame, FrameStep;

	E3dImageSpecs  ImageSpecs;          // Changeable by user

	EpImage*       ScaledBackgroundImage;

	EList(, E3dRenderer*, Renderers);   // Set of Renderers (one for each registered RendererClass)
	E3dRenderer*   Renderer;            // The current Renderer
	E3dRenderer*   LastRenderer;        // The user can toggle between this one and Renderer (middle button)
	EBool          InteractiveRedraw;
};


extern EResourceList       E3dRendererSet_resources;

EList_s(extern, E3dRendererClassPtr, E3dRenderer_Classes);

extern E3dRendererClass    E3dRenderer_class[];
extern E3dRendererClass    E3dRayTracer_class[];

extern E3dRendererClass*   E3dWireframeRenderer_class;
extern E3dRendererClass*   E3dWireAndZRenderer_class;
extern E3dRendererClass*   E3dHiddenLineRenderer_class;
extern E3dRendererClass*   E3dShadedSolidRenderer_class;
extern E3dRendererClass*   E3dGLSLRenderer_class;



extern const char*    E3dGL_SemanticCULL_FACE_ENABLE;
extern const char*    E3dGL_SemanticDEPTH_TEST_ENABLE;
extern const char*    E3dGL_SemanticBLEND_SRC_FACTOR;
extern const char*    E3dGL_SemanticBLEND_DST_FACTOR;



// Callback struct for 3DWindow redraws
//
typedef struct
{
	ECallbackStructCore;

	E3dScene*     Scene;
	E3dModel*     CameraModel;E3dCamera*  Camera;
	EChangeFlags	SceneChanged;
	EChangeFlags	RendererChanged;
} E3dDrawCallbackStruct;

extern ECoreClass  E3dDrawCallbackStruct_class[];


#include <math.h>
#include <E3D/Math.h>


#define _E3d_ShadeSurface(pShadeSurfaceFunc,  pSurfacePoint,  pSurfaceMaterial, pVolumeShader, pShaderBeyondSurface,  pLightSamples, pNLightSamples,\
 pSpectralIndex, pNSpectralSamples,  pResult) do\
{\
	E3dSurfacePoint*  lSurfacePoint = pSurfacePoint;\
	const E3dShader*  lSurfaceShader = pSurfaceMaterial->Shader;\
	uint8_t  lTmpShader[lSurfaceShader->Class->StructSize];\
\
	const E3dShader*  lTextureModulatedSurfaceShader = E3dShader_ModulateWithTextures(pSurfaceMaterial, lSurfaceShader, (E3dShader*)&lTmpShader,\
	 lSurfacePoint->TextureColors,  pSpectralIndex, pNSpectralSamples);\
\
	pShadeSurfaceFunc(NULL,  lSurfacePoint,  pSurfaceMaterial, lTextureModulatedSurfaceShader, pVolumeShader, pShaderBeyondSurface,  pLightSamples, pNLightSamples,  pResult);\
} while(0)


#define E3dRenderer_ApplyChange(pField, pChangeFlags, pRendererChangeFlags) do\
{\
	if(LSettingsPanel->ApplyChangesToAllWindows)\
	{\
		EIndex  lPI = 0;for(;lPI < E3dp_Panels_Count;lPI++)\
		{\
			E3dPanel*  l3DPanel = E3dp_Panels[lPI];EIndex  lWC = 0;const EIndex  lWN = l3DPanel->Windows_Count;for(;lWC < lWN;lWC++)\
			{\
				E3dWindow*  l3DWindow = l3DPanel->Windows[lWC];E3dpWindow*  l3DpWindow = E_DynamicCast(l3DWindow, E3dpWindow);if(l3DpWindow && l3DpWindow != LEdited3DWindow)\
				{\
					if(l3DWindow->StatusFlags & E3dWF_CREATED)\
					{\
						if(l3DpWindow->RendererSet->Renderer && LEdited3DWindow->RendererSet->Renderer)\
						{\
							const E3dRenderer*  lSrcRenderer = LEdited3DWindow->RendererSet->Renderer;\
							E3dRenderer*        lDstRenderer = l3DpWindow->RendererSet->Renderer;\
							lDstRenderer->pField = lSrcRenderer->pField;\
						}\
					}\
				}\
			}\
		}\
		E3dWindow_Redraw((E3dWindow*)LEdited3DWindow,  pChangeFlags, pRendererChangeFlags,  TRUE);\
		E3dWindows_RedrawByChangeFlagsExt(pChangeFlags, (E3dWindow*)LEdited3DWindow, FALSE, TRUE);\
	}\
	else E3dWindow_Redraw((E3dWindow*)LEdited3DWindow,  pChangeFlags, pRendererChangeFlags,  TRUE);\
} while(0)



//----------------------------------------
// Rendering/Rendering.c
//----------------------------------------
extern E3dDrawingStage*   E3dRenderer_DrawingStageByName(E3dRenderer* PRenderer, const char* PName);

extern int                E3d_ShadowPixelType, E3d_ShadowComponentsPerPixel, E3d_ShadowPixelComponentType;
extern void               E3dp_RenderingHWInfoInit(EpGPUInfo* PHardwareInfo);
extern void               E3d_RenderingInit(void);

extern void               _E3d_ImageSpecsCopy(E3dImageSpecs* PSrc, E3dImageSpecs* PDst);

extern void               E3d_RendererUpdateSpectrum(E3dRenderer* PRenderer);

extern E3dRendererClass*  E3dRenderer_ClassByName(const char* PName);
extern E3dRendererClass*  E3dRenderer_ClassRegister(E3dRendererClass* PTemplate);
extern E3dRendererClass*  E3dRenderer_ClassRegisterInactive(const char* PName);
extern void               E3dRenderer_ClassDeactivate(E3dRendererClass* PClass);
extern void               E3dRenderer_ClassesFree(void);
extern EIndex             E3dRenderer_ClassActiveSubClasses(E3dRendererClass* PClass);


extern E3dRenderer*     E3dRenderer_New(E3dRendererClass* PRendererClass);
extern E3dRenderer*     E3dRenderer_CloneNew(E3dRendererClass* PRendererClass);

extern void             E3dRender_QueueClear(E3dChanges* PEntry);
extern void             E3dRenderer_ClearChangeQueues(E3dRenderer* PRenderer);

extern void             E3dRenderer_ImagesFree(E3dRenderer* PRenderer);
extern void             E3dRenderer_FreeCaches(E3dRenderer* PRenderer);

extern void             E3dRenderer_ThreadsFree(E3dRenderer* PRenderer, const E3dChanges* PChanges, const EBool PFreeArray);

extern void             E3dRenderer_DrawingStagesReset(E3dRenderer* PRenderer);

extern int              E3dRenderer_Finish(E3dRenderer* PRenderer, const ETime PPollMicroseconds, const ETime PTimeoutMilliseconds, const EBool PSyncGUI);
extern EBool            E3dRenderer_Stop(E3dRenderer* PRenderer);
extern void             E3dRenderers_Stop(void);
extern void             E3dRenderers_FreeCaches(void);
extern void             E3dRenderers_MarkChanges(const EChangeFlags PSceneChangeFlags);

extern void             E3dRendererSet_Init(E3dRendererSet* PRendererSet);
extern E3dRendererSet*  E3dRendererSet_New(void);
extern int              E3dRendererSet_InitToRayTracer(E3dRendererSet* PRendererSet);
extern E3dRenderer*     E3dRendererSet_RendererByClass(const E3dRendererSet* PRendererSet, const E3dRendererClass* PRendererClass);

extern void             E3dRendererSet_CreateRenderers(E3dRendererSet* PRendererSet);
extern E3dRenderer*     E3dRendererSet_RendererByClassName(E3dRendererSet* PRendererSet, const char* PRendererClassName);
extern void             E3dRendererSet_MarkChanges(E3dRendererSet* PRendererSet, E3dRenderer* PExceptThisRenderer, const EChangeFlags PSceneChangedFlags);
extern void             E3dRenderer_CheckImageBuffers(E3dRenderer* PRenderer);
extern void             E3d_RendererUpdatePixelRemappingGrids(E3dRenderer* PRenderer);

extern EBool            E3dRenderer_NeedsToneMapping(const E3dRenderer* PRenderer);
extern E3dDisplayFlags  E3dRenderer_DisplayFlags(E3dRenderer* PRenderer);

extern EBool            E3d_MultiThreadedRendererRedraw(E3dWindow* P3DWindow, E3dRayTracer* PRenderer, E3dScene* PScene);

extern void       E3dModels_ShadowMatrix(E3dModel** PModels, const EIndex PNumOfModels,  E3dShadowMap* PShadowMap, E3dRLightInstance* PLight,
                   E3dMatrix PFrustumMatrix, E3dMatrix PReceiverSTMatrix);


extern int         E3d_TraceRayOpaque(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, E3dStackRay* PRay, int PRayIter, E3dShaderResult* PColor);
extern int         E3d_TraceRay(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, E3dStackRay* PRay, int PRayIter, E3dShaderResult* PColor);

extern int         E3dWindow_RenderFrameWithRenderer(E3dWindow* P3DWindow, E3dScene* PScene, E3dRenderer* PRenderer,  E3dCamera* PCamera, E3dModel* PCameraModel,
                    const EBool PSwapBuffers);

extern void        E3dRenderer_FreeLightSamples(E3dRenderer* PRenderer);

extern E3dCoord2*  E3d_CreateStratifiedSamples2D(const EIndex PNumOfSamples, const char PType, ERandomState* PState, E3dCoord2* PSamples);


extern void        E3d_HemisphereSampleCosineWeightTBN(const E3dCoordinate PU, const E3dCoordinate PV,
                    const E3dCoord3* PTangent, const E3dCoord3* PBitangent, const E3dCoord3* PNormal,  E3dCoord3* PResult);

extern E3dCoord3*  E3d_CreateSphereSamples(const EIndex PNumOfSphereSamples, const char PType, ERandomState* PRandomState, const EBool PCosineWeight);


extern char*       E3dDisplayFlags_ToString(const E3dDisplayFlags PFlags, char* PString);
extern char*       E3d_ItemFlagsToString(const E3dDisplayFlags PFlags, char* PString);

extern void        E3dGPUAA_Init(E3dGPUAA* PAA);

extern char*       E3dRenderer_StateToString(const E3dRenderer* PRenderer, char* PStr, const EIndex PMaxLen);

extern EBool       E3dChanges_HasChanges(const E3dChanges* PChanges);


//----------------------------------------
// Rendering/Raytracing.c
//----------------------------------------
extern int         E3dRenderer_NSubPixels(const E3dRenderer* PRenderer);
extern EBool       E3dScene_CollectAreaLights(const E3dScene* PScene, E3dRenderer* PRenderer, const EBool PForce);
extern void        E3dScene_SampleLights(const E3dScene* PScene, E3dRenderer* PRenderer, const int PAreaLightType, const EIndex PNumOfSamplesPerLight, const EBool PForce);



//----------------------------------------
// Accelerator.c
//----------------------------------------
typedef struct
{
	EIndex  TotalNumOfPolygons, TotalNumOfTriangles, GlobalTriangleIndex;
} E3dGetRenderTrianglesStats;

extern EObjectClass  E3dAccelerator_class[];

// Flags for E3dObject_GetRenderShapes()
//
#define E3dGrtINVERTED             0x0001
#define E3dGrtDO_INVISIBLE         0x0002
#define E3dGrtDO_WIREFRAME         0x0004
#define E3dGrtSHOW_FACES           0x0008
#define E3dGrtAREALIGHTS_ONLY      0x0010
#define E3dGrtUSE_LOD              0x0020
#define E3dGrtPREP_FOR_RAYTRACING  0x0040  // Prep RTriangles for CPU ray-tracing

extern int     E3dObject_GetRenderShapes(E3dObject* PObject, E3dModel* PModel,  E3dRenderer* PRenderer, E3dMaterialPtrListA* POldMaterials,  const uint16_t PFlags,
                const E3dCoordinate* PVertexMatrix, const E3dCoordinate* PNormalMatrix,  E3dRPolyGroupPtrListA* PRenderPolyGroups, E3dGetRenderTrianglesStats* PStats);

#define E3dWORLD  0x01
#define E3dVIEW   0x02

extern EIndex  E3dScene_GetRenderShapes(E3dScene* PScene, E3dRenderer* PRenderer, const uint16_t PFlags, const EBool PFreeOld, const EBool PAreaLightsOnly);
extern EBool   E3dRenderer_ShapesFree(E3dRenderer* PRenderer);
extern EBool   E3dRPolyGroups_Free(E3dRPolyGroupPtrListA* PRPolyGroups);

extern EIndex  E3dObject_RPolyGroupsUpdate(const E3dObject* PObject, const E3dModel* PModel,  E3dRenderer* PRenderer, E3dMaterialPtrListA* POldMaterials,
                const E3dCoordinate* PVertexMatrix, const E3dCoordinate* PNormalMatrix, E3dRPolyGroupPtrListA* PRPolyGroups, EIndex* PGlobalTriangleIndex);

extern int     E3dObject_RPolyGroupsUpdateMaterials(E3dObject* PObject, E3dModel* PModel,  E3dRenderer* PRenderer, E3dMaterialPtrListA* POldMaterials,
                const E3dCoordinate* PWorldToViewerMatrix, const E3dCoordinate PSizeFactor, E3dRPolyGroupPtrListA* PRPolyGroups);

extern int     E3dScene_RPolyGroupsUpdateMaterials(E3dScene* PScene, E3dRayTracer* PRenderer);

extern void    E3dRenderer_BuildAccelerator(E3dRayTracer* PRenderer);


//----------------------------------------
// Rendering/Lights.c
//----------------------------------------
#define E3dSAMPLING_TILES_PER_SET  42

extern void    E3d_SamplingTilesCreate(void);
extern void    E3d_SamplingTilesFree(void);

extern EIndex  _E3d_AddPointLights(const E3dRenderer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, const E3dSurfacePoint* PPoint,
                const int PSpectralIndex, const int PNSpectralSamples,  E3dShaderLight* PShaderLight);

extern EIndex  _E3d_AddPointLightsSh(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracerThread, const E3dSurfacePoint* PPoint, const void* PExcludeFace,
                const int PSpectralIndex, const int PNSpectralSamples,  E3dShaderLight* PShaderLight);

extern EIndex  E3dAreaLight_NumOfElements(const E3dAreaLight* PAreaLight);

extern EIndex  _E3d_AddAreaLightSh(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, const E3dSurfacePoint* PPoint, const void* PExcludeFace,
                const E3dAreaLight* PLight, const float PShadowBrightness, E3dGridRay* PShadowRay, const EInt32_3* PStartVoxelC,
                const int PSpectralIndex, const int PNSpectralSamples,  const EIndex PShaderLightStartIndex);

extern EIndex  _E3d_AddAreaLights(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, const E3dSurfacePoint* PPoint,
                const int PSpectralIndex, const int PNSpectralSamples, const EIndex PShaderLightStartIndex);

extern EIndex  _E3d_AddAreaLightsSh(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, const E3dSurfacePoint* PPoint, const void* PExcludeFace,
                const int PSpectralIndex, const int PNSpectralSamples, const EIndex PShaderLightStartIndex);

extern EIndex  _E3d_AddAreaLightsOneSampleSh(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, const E3dSurfacePoint* PPoint, const void* PExcludeFace,
                const int PSpectralIndex, const int PNSpectralSamples,  E3dShaderLight* PShaderLight);

extern EIndex  _E3d_AddAreaLights_Volume(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, const E3dSurfacePoint* PPoint,
                const int PSpectralIndex, const int PNSpectralSamples,  const EIndex PLightSampleStartIndex, const EIndex PSampleNumDivider);

extern EIndex  _E3d_AddAreaLightsSh_Volume(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, const E3dSurfacePoint* PPoint, const void* PExcludeFace,
                const int PSpectralIndex, const int PNSpectralSamples,  const EIndex PLightSampleStartIndex, const EIndex PSampleNumDivider);

extern EIndex  _E3d_AddAreaLightsOneSample_Volume(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, const E3dSurfacePoint* PPoint, const void* PExcludeFace,
                const int PSpectralIndex, const int PNSpectralSamples,  E3dShaderLight* PShaderLight);

extern EIndex  _E3d_AddAreaLightsOneSampleSh_Volume(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, const E3dSurfacePoint* PPoint,
                const void* PExcludeFace,  const int PSpectralIndex, const int PNSpectralSamples,  E3dShaderLight* PShaderLight);


extern void    E3d_ShadePoint(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PThread, const E3dRTriangle* PRTriangle, E3dSurfacePoint* PPoint, E3dStackRay* PRay,
                const E3dMaterial* PSurfaceMaterial, const E3dShader* PVolumeShader, const E3dShader* PShaderBeyondSurface,  E3dShaderResult* PResultTmp,
                E3dShaderResult* PResult);

extern void    E3d_ShadePointShadows(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PThread, const E3dRTriangle* PRTriangle, E3dSurfacePoint* PPoint, E3dStackRay* PRay,
                const E3dMaterial* PSurfaceMaterial, const E3dShader* PVolumeShader, const E3dShader* PShaderBeyondSurface,  E3dShaderResult* PResultTmp,
                E3dShaderResult* PResult);

extern void    E3d_ShadePointShadowsNoBRDF(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PThread, const E3dRTriangle* PRTriangle, E3dSurfacePoint* PPoint,
                E3dStackRay* PRay,  const E3dMaterial* PSurfaceMaterial, const E3dShader* PVolumeShader, const E3dShader* PShaderBeyondSurface,
                E3dShaderResult* PResultTmp, E3dShaderResult* PResult);

extern void    E3d_ShadePoint_Volume(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PThread, const E3dRTriangle* PRTriangle, E3dSurfacePoint* PPoint, E3dStackRay* PRay, 
                const E3dMaterial* PSurfaceMaterial, const E3dShader* PVolumeShader, const E3dShader* PShaderBeyondSurface,
                E3dShaderResult* PResultTmp, E3dShaderResult* PResult);

extern void    E3d_ShadePointShadows_Volume(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PThread, const E3dRTriangle* PRTriangle, E3dSurfacePoint* PPoint,
                E3dStackRay* PRay,  const E3dMaterial* PSurfaceMaterial, const E3dShader* PVolumeShader, const E3dShader* PShaderBeyondSurface,
                E3dShaderResult* PResultTmp, E3dShaderResult* PResult);

extern void    E3d_ShadeSurfaceIrradiance(const E3dShaderLight* PLights, const int PNLights,  E3dShaderResult* PResult);

extern void    (*E3d_ShadeSurfaceSphericalHarmonicsIrradianceFunc)(const E3dShaderLight* PLights, const int PNLights,  E3dShaderResult* PResult);
extern void    (*E3d_ShadePointSphericalHarmonicsIrradianceFunc)(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PThread, const E3dRTriangle* PRTriangle,
                E3dSurfacePoint* PSurfacePoint, E3dStackRay* PRay,  const E3dMaterial* PMaterial, const E3dShader* PVolumeShader, const E3dShader* PShaderBeyondSurface,
                E3dShaderResult* PResultTmp, E3dShaderResult* PResult);

extern void    (*E3d_ShadeSurfaceSphericalHarmonicsRadianceFunc)(const E3dShaderLight* PLights, const int PNLights,  E3dShaderResult* PResult);
extern void    (*E3d_ShadePointSphericalHarmonicsRadianceFunc)(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PThread, const E3dRTriangle* PRTriangle,
                E3dSurfacePoint* PSurfacePoint, E3dStackRay* PRay,  const E3dMaterial* PMaterial, const E3dShader* PVolumeShader, const E3dShader* PShaderBeyondSurface,
                E3dShaderResult* PResultTmp, E3dShaderResult* PResult);

extern void    (*E3d_ShadeSurfaceSphericalHarmonicsOcclusionFunc)(const E3dShaderLight* PLights, const int PNLights,  E3dShaderResult* PResult);
extern void    (*E3d_ShadePointSphericalHarmonicsOcclusionFunc)(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PThread, const E3dRTriangle* PRTriangle,
                E3dSurfacePoint* PSurfacePoint, E3dStackRay* PRay,  const E3dMaterial* PMaterial, const E3dShader* PVolumeShader, const E3dShader* PShaderBeyondSurface,
                E3dShaderResult* PResultTmp, E3dShaderResult* PResult);

extern void    E3d_ShadeSurfaceWavelets(const E3dShaderLight* PLights, const int PNLights,  E3dShaderResult* PResult);


extern E3dShaderClassRendererBinding*  E3dShader_ClassAddRenderer(E3dShaderClass* PShaderClass, E3dRendererClass* PRendererClass,
                                        const E3dShaderClassRendererBinding* PDescriptor);

extern E3dShaderClassRendererBinding*  E3dShader_ClassGetRendererBinding(const E3dShaderClass* PShaderClass, const E3dRendererClass* PRendererClass);
extern void                            E3dShader_ClassRemoveRenderer(E3dShaderClass* PShaderClass, const E3dRendererClass* PRendererClass);

// PFlags - which stages are used by the ShaderClass
#define E3dShaderStageVERTEX         0x0001    // Shader class has a hand-wrirtten VertexProgram
#define E3dShaderStageVERTEX_AUTO    0x0002    // Shader class has auto-generated VertexPrograms
#define E3dShaderStageGEOMETRY       0x0004    // Shader class has a hand-wrirtten GeometryProgram
#define E3dShaderStageGEOMETRY_AUTO  0x0008    // Shader class has auto-generated GeometryPrograms
#define E3dShaderStageFRAGMENT       0x0010    // Shader class has a hand-wrirtten FragmentProgram
#define E3dShaderStageFRAGMENT_AUTO  0x0020    // Shader class has auto-generated FragmentPrograms
#define E3dShaderStageALL            0x003F
extern void                            E3dShaderClass_RendererBindingsAdd(E3dShaderClass* PShaderClass, const EResourceList* PResources, const uint32_t PFlags,
                                        const char* PSource);

extern void                            E3dShaderVariant_CoreFree(E3dShaderVariant* PVariant);


// Language-agnostic shader cores for built-in shaders
extern const char*  Material_Constant_core_sl;
extern const char*  Material_Lambert_core_sl;
extern const char*  Material_Phong_core_sl;


extern EResourceList  E3dSL_ConstantResources;
extern EResourceList  E3dSL_LambertResources;
extern EResourceList  E3dSL_PhongResources;


extern E3dShaderVariant*  (*E3dShader_GenerateCode)(const E3dModel* PModel, const E3dGeometry* PGeometry, const EObject* PShadeable,
 E3dRenderer* PRenderer,
 E3dShaderClassRendererBinding* PShaderClassBinding,
 const E3dShaderPassesMask PPassesMask,
 const E3dRLightInstance* PLights, const EIndex PNLights, E3dShadowMap** PShadowMaps,
 EResourceList* PFrgResources, EResourceList* PVtxResources,
 const EBool PAddComments);

extern void  E3dShader_UpdateNormalMap(E3dScene* PScene, E3dRenderer* PRenderer, E3dMaterial* PMaterial, E3dShader* PShader,
              E3d2DTexture* PTexture, const E3dTextureMapper* PTextureMapper);

extern EBool E3dMaterial_SLUpdateNormalMaps(E3dScene* PScene, E3dRenderer* PRenderer, E3dMaterial* PMaterial,
              const E3dTextureMapperPtrList* PTextureMappers, const E3dTextureMapperPtrList* PTextureMapperRefs);


extern void  (*E3dShader_GLUpdateNormalMap)(E3dScene* PScene, E3dRenderer* PRenderer, E3dMaterial* PMaterial, E3dShader* PShader,
              E3d2DTexture* PTexture, const E3dTextureMapper* PMapper);

extern void  E3dNodeHrc_GLDraw(E3dWindow* P3DWindow, E3dScene* PScene, E3dRenderer* PRenderer, const uint32_t PSubCameraID, E3dDrawContext* PDrawContext,
              E3dNode* PRootNode, const E3dMatrix PWorldToViewerMatrix, const EBool PDrawCamera);

extern void  E3dGL_WindowSetViewTransform(E3dWindow* P3DWindow, E3dRenderer* PRenderer, E3dScene* PScene, const uint32_t PSubCameraID, const EIndex PAAMult,
              E3dMatrix PWorldToViewerMatrix);


//----------------------------------------
// GL/DrawMesh.c
//----------------------------------------
extern int  E3dGL_DrawUnlit(E3dRenderer* PRenderer, const E3dDrawContext* PDrawContext, E3dGPUContext* PGPUContext,
             E3dScene* PScene, E3dModel* PModel, E3dGeometry* PGeometry, EObject* PShadeable);

extern int  E3dGL_DrawLambert(E3dRenderer* PRenderer, const E3dDrawContext* PDrawContext, E3dGPUContext* PGPUContext,
             E3dScene* PScene, E3dModel* PModel, E3dGeometry* PGeometry, EObject* PShadeable);

extern int  E3dGL_DrawPhong(E3dRenderer* PRenderer, const E3dDrawContext* PDrawContext, E3dGPUContext* PGPUContext,
             E3dScene* PScene, E3dModel* PModel, E3dGeometry* PGeometry, EObject* PShadeable);


//----------------------------------------
// GL/FrameBuffer.c
//----------------------------------------
enum
{
	E3dBloom2D_POWER = 1,
	E3dBloomSEPARABLE_POWER,
	E3dBloomSEPARABLE_GAUSSIAN,
};

extern void  E3dGL_UpdateBloomKernel(E3dRenderer* PRenderer, E3dBloom* PBloom, const int PBloomRadius);

//----------------------------------------
// E3D/Panel/Renderer.c
//----------------------------------------
extern void  E3dRenderer_UpdateStats(E3dRenderer* PRenderer);
extern void  E3dRenderer_UpdateStatsAndSync(E3dRenderer* PRenderer);
extern void  E3dDrawContext_UpdateFromRenderer(E3dDrawContext* PDrawContext, const E3dRenderer* PRenderer);


//----------------------------------------
// SLCommon.ci
//----------------------------------------
extern int E3dSRenderer_ClassFree(E3dRendererClass* PClass);

typedef struct E3dPolyGroupComponentRenderCustomShape_S  E3dPolyGroupComponentRenderCustomShape;

#define E3dPolyGroupComponentRenderCustomShapeCore\
	E3dPolyGroupComponentCore;\
\
	E3dBBox  BBox;\
\
	EBool  (*TraceRayClosestHit)(E3dRay* PRay,  const E3dRPolyGroup* PRPolyGroup, E3dPolyGroupComponentRenderCustomShape* PComponentRenderCustomShape,  E3dRayHit* PRayHit);\
	EBool  (*TraceShadowRay)(E3dRay* PRay,  const E3dRPolyGroup* PRPolyGroup, E3dPolyGroupComponentRenderCustomShape* PComponentRenderCustomShape, const E3dCoordinate PMaxDistance);\
	EBool  (*RayMarchVolume)(const E3dIRay* PRay, const E3dRayHit* PRayHit,  const E3dRPolyGroup* PRPolyGroup, E3dPolyGroupComponentRenderCustomShape* PVolumePG,\
        	 E3dShaderResult* PResult);\
\
	void   (*SampleSurface)(const E3dRPolyGroup* PRPolyGroup, E3dPolyGroupComponentRenderCustomShape* PPolyGroupComponent,  E3dSurfacePoint* PSurfacePoint)


struct E3dPolyGroupComponentRenderCustomShape_S
{
	E3dPolyGroupComponentRenderCustomShapeCore;
};

// PolyGroupComponent baseclass for rendering custom shapes like implicit surfaces, SDFs etc.
//
extern E3dPolyGroupComponentClass  E3dPolyGroupComponentRenderCustomShape_class[];


//========================================================
// Sign-agnostic Fresnel term (PNdotV may be negative)
// Schlick's approximation
//========================================================
static inline float E3d_Fresnel(const float PCurrentRefractiveIndex, const float PRefractiveIndex, const float PNdotV)
{
/*
	const float  Leta = PCurrentRefractiveIndex / PRefractiveIndex;
	const float  LT0 = Leta - 1.0, LT1 = Leta + 1.0,
	             LR0 = LT0 * LT0 / (LT1 * LT1);
*/

	const float  LT0 = PCurrentRefractiveIndex - PRefractiveIndex, LT1 = PCurrentRefractiveIndex + PRefractiveIndex, LT2 = LT0 / LT1,
	             LR0 = LT2 * LT2;  // Reflectivity at normal incidence


//printf("%s() %s:%d  %f %f %f\n", __FUNCTION__, __FILE__, __LINE__, LT0, LT1, LR0);fflush(stdout);

// Simplified Fresnel term (Schlick's approximation)
//
	const float  c = 1.0 - fabsf(PNdotV);
	return(LR0 + (1.0 - LR0) * c * c * c * c * c);

// The full Fresnel term
//
/*
	const float  c = 1.0 - fabsf(PNdotV);
	float        g = E3d_Sqrt(PRefractiveIndex * PRefractiveIndex + c * c - 1.0), gPc = g + c, gMc = g - c;

	return(0.5 * gMc * gMc / (gPc * gPc) * (1.0 + (c * gPc - 1.0) * (c * gPc - 1.0) / ((c * gMc + 1.0) * (c * gMc + 1.0))));
*/
}


//========================================================
// Fresnel term for conductors (metal)
//========================================================
static inline void E3d_MetalFresnel(const float* PEtas,
 const float* Pks,
 const int PNSpectralSamples,
 const E3dCoordinate PCosI,
 float* PReflectivity)
{
	const E3dCoordinate  LCosI2 = PCosI * PCosI;

	int  LI = 0;for(;LI < PNSpectralSamples;LI++)
	{
		const E3dCoordinate  LEta = PEtas[LI], Lk = Pks[LI],
		                     LTmpF = LEta * LEta + Lk * Lk, LTmp  = LTmpF * LCosI2,
		                     L2EtaCosI = 2.0 * LEta * PCosI;

		const E3dCoordinate  LRparl2 = (LTmp  - (L2EtaCosI) + 1.0)    / (LTmp  + (L2EtaCosI) + 1.0);
		const E3dCoordinate  LRperp2 = (LTmpF - (L2EtaCosI) + LCosI2) / (LTmpF + (L2EtaCosI) + LCosI2);

		E3dCoordinate        LF = (LRparl2 + LRperp2) * 0.5;if(LF < 0.0) LF = 0.0;

		PReflectivity[LI] = LF;
	}
}


//========================================================
// Fresnel term for dielectrics
//========================================================
static inline void E3d_DielectricFresnel(const float* PEtas_i, const float* PEtas_t,  const int PNSpectralSamples,  const E3dCoordinate PCosI,  float* PReflectivity)
{
	const E3dCoordinate  L2 = E3d_Sqrt(E_Max(0.0, 1.0 - PCosI * PCosI));
	const E3dCoordinate  LCosI = E3d_Abs(PCosI);

	int  LI = 0;for(;LI < PNSpectralSamples;LI++)
	{
		const float          LEtaI = PEtas_i[LI], LEtaT = PEtas_t[LI];
		const E3dCoordinate  LSinT = LEtaI / LEtaT * L2;	// Compute sin t with Snell's law

		if(LSinT >= 1.0)
		{
// Handle total internal reflection
//
			PReflectivity[LI] = 1.0;
		}
		else
		{
			const E3dCoordinate  LCosT = E3d_Sqrt(E_Max(0.0, 1.0 - LSinT * LSinT));

			const E3dCoordinate  LEtaTxLCosI = LEtaT * LCosI,
			                     LEtaIxLCosI = LEtaI * LCosI,
			                     LEtaIxLCosT = LEtaI * LCosT,
			                     LEtaTxLCosT = LEtaT * LCosT;

			const E3dCoordinate  LRParl = (LEtaTxLCosI - LEtaIxLCosT) / (LEtaTxLCosI + LEtaIxLCosT);
			const E3dCoordinate  LRPerp = (LEtaIxLCosI - LEtaTxLCosT) / (LEtaIxLCosI + LEtaTxLCosT);

			PReflectivity[LI] = (LRParl * LRParl + LRPerp * LRPerp) * 0.5;
		}
	}
}


//========================================================
// Dielectric Fresnel term for shaders
//========================================================
static inline void E3d_ShaderFresnel(E3dSurfacePoint* PPoint, const E3dShader* PVolumeShader, const E3dShader* PShaderBeyondSurface,
 const int PSpectralIndex, const int PNSpectralSamples,  E3dShaderResult* PResult)
{
	const E3dCoord3      LNormal = PPoint->Normal;
	const E3dCoord4      LPointToViewer = PPoint->PointToViewer;
	const E3dCoordinate  LNdotV = E_Dot3_vv(LNormal, LPointToViewer);

	if(0)
	{
// Old Fresnel term (Schlick's approximation)
//
		const float  LCurrentRefractiveIndex = PVolumeShader ? PVolumeShader->RefractiveIndex.Samples[PSpectralIndex] : 1.0;
		const float  LNextRefractiveIndex    = PShaderBeyondSurface ? PShaderBeyondSurface->RefractiveIndex.Samples[PSpectralIndex] : 1.0;
		const float  LFresnel = E3d_Fresnel(LCurrentRefractiveIndex, LNextRefractiveIndex, LNdotV);
		const float  LNFresnel = 1.0 - LFresnel;

		{
			E3dM_SpectralLoop(PSpectralIndex, PNSpectralSamples)
			{
				PResult->Reflective[LSI] *= LFresnel;PResult->Transparent[LSI] *= LNFresnel;
			}
		}
	}
	else
	{
// Physically accurate dielectric Fresnel term (i.e. glass)
//
		float  LFresnel[PNSpectralSamples];
		E3d_DielectricFresnel(PVolumeShader ? PVolumeShader->RefractiveIndex.Samples + PSpectralIndex : E3d_Ones,
		 PShaderBeyondSurface ? PShaderBeyondSurface->RefractiveIndex.Samples + PSpectralIndex : E3d_Ones,  PNSpectralSamples,  LNdotV,  LFresnel);

		{
			E3dM_SpectralLoop(PSpectralIndex, PNSpectralSamples)
			{
				const float  LFresnelV = LFresnel[LSC];
				PResult->Reflective[LSI] *= LFresnelV;PResult->Transparent[LSI] *= 1.0 - LFresnelV;
			}
		}
	}
}


//========================================================
// Fresnel term for dielectrics - enter/exit check
//========================================================
static inline void E3d_DielectricFresnelDirCheck(const float* PEtas_i, const float* PEtas_t, const int PNSpectralSamples, const E3dCoordinate PCosI, float* PReflectivity)
{
	const EBool   LEntering = PCosI > 0.0;
	const float*  LEtas_i;
	const float*  LEtas_t;

//assert(PCosi >= -1.0 && PCosi <= 1.0);
	if(LEntering) { LEtas_i = PEtas_i;LEtas_t = PEtas_t; }
	else { LEtas_i = PEtas_t;LEtas_t = PEtas_i; }

	{
		const E3dCoordinate  L2 = E3d_Sqrt(E_Max(0.0, 1.0 - PCosI * PCosI));
		const E3dCoordinate  LCosI = E3d_Abs(PCosI);

		int  LI = 0;for(;LI < PNSpectralSamples;LI++)
		{
			const float          LEtaI = LEtas_i[LI], LEtaT = LEtas_t[LI];
			const E3dCoordinate  LSinT = LEtaI / LEtaT * L2;	// Compute sin t with Snell's law

			if(LSinT >= 1.0)
			{
// Handle total internal reflection
//
				PReflectivity[LI] = 1.0;
			}
			else
			{
				const E3dCoordinate  LCosT = E3d_Sqrt(E_Max(0.0, 1.0 - LSinT * LSinT));

				const E3dCoordinate  LEtaTxLCosI = LEtaT * LCosI,
				                     LEtaIxLCosI = LEtaI * LCosI,
				                     LEtaIxLCosT = LEtaI * LCosT,
				                     LEtaTxLCosT = LEtaT * LCosT;

				const E3dCoordinate  LRParl = (LEtaTxLCosI - LEtaIxLCosT) / (LEtaTxLCosI + LEtaIxLCosT);
				const E3dCoordinate  LRPerp = (LEtaIxLCosI - LEtaTxLCosT) / (LEtaIxLCosI + LEtaTxLCosT);

				PReflectivity[LI] = (LRParl * LRParl + LRPerp * LRPerp) * 0.5;
			}
		}
	}
}


#if defined (__cplusplus) && (!(defined (__CPLUSLIBS)))
}	// Close scope of 'extern "C"' declaration
#endif

#endif	// _E3D_Rendering_h
