/**
* Copyright (C) 2009-2014 Cars and Tracks Development Project (CTDP).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package net.ctdp.rfdynhud.render;
import java.nio.ByteBuffer;
import net.ctdp.rfdynhud.gamedata.LiveGameData;
import net.ctdp.rfdynhud.gamedata.__GDPrivilegedAccess;
import net.ctdp.rfdynhud.properties.Position;
import net.ctdp.rfdynhud.util.RFDHLog;
import net.ctdp.rfdynhud.valuemanagers.Clock;
import net.ctdp.rfdynhud.valuemanagers.TimeBasedClock;
import net.ctdp.rfdynhud.widgets.WidgetsConfiguration;
import net.ctdp.rfdynhud.widgets.__WCPrivilegedAccess;
import net.ctdp.rfdynhud.widgets.base.widget.Widget;
import net.ctdp.rfdynhud.widgets.base.widget.WidgetController;
import net.ctdp.rfdynhud.widgets.base.widget.__WPrivilegedAccess;
/**
* The {@link WidgetsDrawingManager} handles the drawing of all visible widgets.
*
* @author Marvin Froehlich (CTDP)
*/
public class WidgetsDrawingManager
{
private final boolean oneTextureForAllWidgets;
private TransformableTexture[] textures;
private TransformableTexture[] widgetTextures;
private TransformableTexture[][] widgetSubTextures = null;
private final ByteBuffer textureInfoBuffer = TransformableTexture.createByteBuffer();
private long frameCounter = 0L;
private final Clock clock = new TimeBasedClock( 50000000L ); // 50 ms
private final WidgetsConfiguration widgetsConfig;
private final WidgetsManager renderListenersManager = new WidgetsManager();
public final WidgetsManager getRenderListenersManager()
{
return ( renderListenersManager );
}
public final WidgetsConfiguration getWidgetsConfiguration()
{
return ( widgetsConfig );
}
public void resizeMainTexture( int gameResX, int gameResY )
{
if ( oneTextureForAllWidgets )
{
if ( ( textures[0].getWidth() != gameResX ) || ( textures[0].getHeight() != gameResY ) )
textures[0] = TransformableTexture.createMainTexture( gameResX, gameResY, false );
}
__GDPrivilegedAccess.setGameResolution( gameResX, gameResY, widgetsConfig );
__WCPrivilegedAccess.setViewport( 0, 0, gameResX, gameResY, widgetsConfig );
}
public final TextureImage2D getMainTexture( int widgetIndex )
{
if ( oneTextureForAllWidgets )
return ( textures[0].getTexture() );
TransformableTexture tt = widgetTextures[widgetIndex];
if ( tt == null )
return ( null );
return ( tt.getTexture() );
}
public int collectTextures( LiveGameData gameData, boolean isEditorMode )
{
int numTextures = 0;
final int numWidgets = widgetsConfig.getNumWidgets();
if ( oneTextureForAllWidgets )
{
numTextures = 1;
textures[0].generateRectanglesForOneBigTexture( gameData, isEditorMode, widgetsConfig );
}
else
{
numTextures = 0;
textures = new TransformableTexture[ numWidgets ];
widgetTextures = new TransformableTexture[ numWidgets ];
for ( int i = 0; i < numWidgets; i++ )
{
Widget widget = widgetsConfig.getWidget( i );
if ( widget.hasMasterCanvas( isEditorMode ) )
{
textures[numTextures] = TransformableTexture.createMainTexture( widget.getMaxWidth( gameData, isEditorMode ), widget.getMaxHeight( gameData, isEditorMode ), true );
textures[numTextures].setTranslation( widget.getPosition().getEffectiveX(), widget.getPosition().getEffectiveY() );
widgetTextures[i] = textures[numTextures];
numTextures++;
}
else
{
widgetTextures[i] = null;
}
}
}
widgetSubTextures = new TransformableTexture[ numWidgets ][];
int j = 0;
for ( int i = 0; i < numWidgets; i++ )
{
Widget widget = widgetsConfig.getWidget( i );
if ( !oneTextureForAllWidgets && widget.hasMasterCanvas( isEditorMode ) )
{
j++;
}
TransformableTexture[] subTextures = widget.getSubTextures( gameData, isEditorMode, widget.getSize().getEffectiveWidth() - widget.getBorder().getInnerLeftWidth() - widget.getBorder().getInnerRightWidth(), widget.getSize().getEffectiveHeight() - widget.getBorder().getInnerTopHeight() - widget.getBorder().getInnerBottomHeight() );
if ( ( subTextures != null ) && ( subTextures.length > 0 ) )
{
widgetSubTextures[i] = subTextures;
if ( textures.length < numTextures + subTextures.length )
{
TransformableTexture[] tmp = new TransformableTexture[ numTextures + subTextures.length ];
System.arraycopy( textures, 0, tmp, 0, numTextures );
textures = tmp;
}
//System.arraycopy( subTextures, 0, textures, numTextures, subTextures.length );
// Shift succeeding textures, so that we can insert sub textures.
System.arraycopy( textures, j, textures, j + subTextures.length, numTextures - j );
// Insert sub textures.
System.arraycopy( subTextures, 0, textures, j, subTextures.length );
j += subTextures.length;
numTextures += subTextures.length;
}
else
{
widgetSubTextures[i] = null;
}
}
if ( numTextures > TransformableTexture.MAX_NUM_TEXTURES )
{
RFDHLog.error( "WARNING: Number of displayed textures truncated. Possible reason: maxOpponents = " + gameData.getModInfo().getMaxOpponents() );
numTextures = TransformableTexture.MAX_NUM_TEXTURES;
}
if ( numTextures < textures.length )
{
TransformableTexture[] tmp = new TransformableTexture[ numTextures ];
System.arraycopy( textures, 0, tmp, 0, numTextures );
textures = tmp;
}
textureInfoBuffer.position( 0 );
textureInfoBuffer.limit( textureInfoBuffer.capacity() );
textureInfoBuffer.put( (byte)textures.length );
int rectOffset = 0;
for ( int i = 0; i < textures.length; i++ )
{
rectOffset = textures[i].fillBuffer( true, 0, 0, i, rectOffset, textureInfoBuffer ); // offsets are irrelevant here
textures[i].setDirty();
}
textureInfoBuffer.position( textures.length * TransformableTexture.STRUCT_SIZE );
textureInfoBuffer.flip();
return ( textures.length );
}
public void clearCompleteTexture()
{
for ( int i = 0; i < textures.length; i++ )
{
TextureImage2D texture = textures[i].getTexture();
if ( textures[i].isDynamic() )
{
texture.getTextureCanvas().setClip( 0, 0, texture.getWidth(), texture.getHeight() );
texture.clear( true, null );
}
}
for ( int i = 0; i < widgetsConfig.getNumWidgets(); i++ )
{
widgetsConfig.getWidget( i ).forceCompleteRedraw( true );
widgetsConfig.getWidget( i ).forceReinitialization();
// TODO: We possibly need to set this texture dirty!
}
}
public void refreshTextureInfoBuffer( boolean isEditorMode, LiveGameData gameData, boolean newConfig )
{
textureInfoBuffer.position( 0 );
textureInfoBuffer.limit( textureInfoBuffer.capacity() );
textureInfoBuffer.put( (byte)textures.length );
final int n = widgetsConfig.getNumWidgets();
if ( oneTextureForAllWidgets )
{
int j = 0;
for ( int i = 0; i < n; i++ )
{
Widget widget = widgetsConfig.getWidget( i );
if ( widget.hasMasterCanvas( isEditorMode ) )
textures[0].setRectangleVisible( j++, widget.isVisible() );
}
}
int k = 0;
int rectOffset = 0;
int testRectOffset = 0;
if ( oneTextureForAllWidgets )
{
rectOffset = textures[0].fillBuffer( true, 0, 0, k++, rectOffset, textureInfoBuffer );
testRectOffset += textures[0].getNumUsedRectangles();
}
else
{
for ( int i = 0; i < n; i++ )
{
if ( widgetTextures[i] != null )
{
Widget widget = widgetsConfig.getWidget( i );
boolean dirty = false;
if ( widget.getWidgetController() != null )
{
Position position = widget.getPosition();
widgetTextures[i].setTranslation( position.getEffectiveX(), position.getEffectiveY() );
dirty = widgetTextures[i].isDirty();
if ( !position.isBaked() )
position.bake();
}
rectOffset = widgetTextures[i].fillBuffer( widget.isVisible(), 0, 0, k++, rectOffset, textureInfoBuffer );
if ( widgetSubTextures[i] != null )
k += widgetSubTextures[i].length; // skip sub textures
testRectOffset += widgetTextures[i].getNumUsedRectangles();
if ( dirty )
{
TransformableTexture[] subTextures = widgetSubTextures[i];
if ( subTextures != null )
{
for ( int j = 0; j < subTextures.length; j++ )
{
subTextures[j].setDirty();
}
}
}
}
else if ( widgetSubTextures[i] != null )
{
k += widgetSubTextures[i].length; // skip sub textures
}
}
}
k = 0;
for ( int i = 0; i < widgetSubTextures.length; i++ )
{
if ( !oneTextureForAllWidgets && ( widgetTextures[i] != null ) )
k++; // skip widget texture
Widget widget = widgetsConfig.getWidget( i );
TransformableTexture[] subTextures = widgetSubTextures[i];
if ( subTextures != null )
{
int offX = widget.getPosition().getEffectiveX() + widget.getBorder().getInnerLeftWidth();
int offY = widget.getPosition().getEffectiveY() + widget.getBorder().getInnerTopHeight();
for ( int j = 0; j < subTextures.length; j++ )
{
rectOffset = subTextures[j].fillBuffer( widget.isVisible(), offX + subTextures[j].getOffsetXToRootMasterWidget(), offY + subTextures[j].getOffsetYToRootMasterWidget(), k++, rectOffset, textureInfoBuffer );
testRectOffset += subTextures[j].getNumUsedRectangles();
}
}
}
textureInfoBuffer.position( textures.length * TransformableTexture.STRUCT_SIZE );
textureInfoBuffer.flip();
if ( newConfig && ( rectOffset < testRectOffset ) )
{
RFDHLog.error( "WARNING: Number of displayed textures truncated. Possible reason: maxOpponents = " + gameData.getModInfo().getMaxOpponents() );
}
}
public final int getNumTextures()
{
return ( textures.length );
}
public final ByteBuffer getTextureInfoBuffer()
{
return ( textureInfoBuffer );
}
public final TransformableTexture getTexture( int textureIndex )
{
return ( textures[textureIndex] );
}
public void onCockpitEntered( LiveGameData gameData )
{
clock.init( gameData.getScoringInfo().getSessionNanos() );
frameCounter = 0L;
}
private static final boolean isWidgetReady( Widget widget, boolean hasWaitingWidgets, LiveGameData gameData )
{
boolean ready = true;
if ( hasWaitingWidgets )
{
int neededData = widget.getNeededData();
if ( ( ( neededData & Widget.NEEDED_DATA_TELEMETRY ) != 0 ) && !gameData.getTelemetryData().isUpdatedInTimeScope() )
ready = false;
else if ( ( ( neededData & Widget.NEEDED_DATA_SCORING ) != 0 ) && !gameData.getScoringInfo().isUpdatedInTimeScope() )
ready = false;
//else if ( ( ( neededData & Widget.NEEDED_DATA_SETUP ) != 0 ) && !gameData.getSetup().isUpdatedInTimeScope() )
// ready = false;
}
return ( ready );
}
/**
* Draws all visible {@link Widget}s in the list.
*
* @param gameData the live game data
* @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor
* @param hasWaitingWidgets
* @param completeRedrawForced complete redraw forced?
*/
public void drawWidgets( LiveGameData gameData, boolean isEditorMode, boolean hasWaitingWidgets, boolean completeRedrawForced )
{
long sessionNanos = gameData.getScoringInfo().getSessionNanos();
clock.update( sessionNanos, frameCounter, completeRedrawForced );
frameCounter++;
renderListenersManager.fireBeforeWidgetsAreRendered( gameData, widgetsConfig, sessionNanos, frameCounter );
if ( !widgetsConfig.isValid() )
return;
__WCPrivilegedAccess.checkFixAndBakeConfiguration( widgetsConfig, isEditorMode );
final int n = widgetsConfig.getNumWidgets();
for ( int i = 0; i < n; i++ )
{
Widget widget = widgetsConfig.getWidget( i );
WidgetController controller = widget.getWidgetController();
if ( controller != null )
{
try
{
controller.update( widget, gameData );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
__GDPrivilegedAccess.setControlledVSIs( gameData.getScoringInfo(), __WPrivilegedAccess.getControlledViewedVSI( controller ), __WPrivilegedAccess.getControlledCompareVSI( controller ) );
}
else
{
__GDPrivilegedAccess.setControlledVSIs( gameData.getScoringInfo(), null, null );
}
TextureImage2D texture = getMainTexture( i );
if ( texture != null )
texture.getTextureCanvas().setClip( 0, 0, texture.getWidth(), texture.getHeight() );
try
{
if ( isWidgetReady( widget, hasWaitingWidgets, gameData ) )
{
__WPrivilegedAccess.updateVisibility( widget, gameData, isEditorMode );
if ( isEditorMode )
{
if ( widget.getDirtyFlag( true ) )
{
widget.drawWidget( clock, completeRedrawForced, gameData, isEditorMode, texture, !oneTextureForAllWidgets );
}
}
else if ( !widget.isVisible() && widget.visibilityChangedSinceLastDraw() )
{
int offsetX = oneTextureForAllWidgets ? widget.getPosition().getEffectiveX() : 0;
int offsetY = oneTextureForAllWidgets ? widget.getPosition().getEffectiveY() : 0;
widget.clearRegion( texture, offsetX, offsetY );
}
}
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
if ( !isEditorMode )
{
for ( int i = 0; i < n; i++ )
{
Widget widget = widgetsConfig.getWidget( i );
TextureImage2D texture = getMainTexture( i );
if ( texture != null )
texture.getTextureCanvas().setClip( 0, 0, texture.getWidth(), texture.getHeight() );
try
{
if ( isWidgetReady( widget, hasWaitingWidgets, gameData ) )
{
if ( widget.isVisible() )
{
WidgetController controller = widget.getWidgetController();
if ( controller != null )
__GDPrivilegedAccess.setControlledVSIs( gameData.getScoringInfo(), __WPrivilegedAccess.getControlledViewedVSI( controller ), __WPrivilegedAccess.getControlledCompareVSI( controller ) );
else
__GDPrivilegedAccess.setControlledVSIs( gameData.getScoringInfo(), null, null );
widget.drawWidget( clock, completeRedrawForced, gameData, isEditorMode, texture, !oneTextureForAllWidgets );
}
}
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
__GDPrivilegedAccess.setControlledVSIs( gameData.getScoringInfo(), null, null );
}
/**
* Creates a new {@link WidgetsDrawingManager}.
*
* @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor
* @param gameResX the game x-resolution (current viewport)
* @param gameResY the game y-resolution (current viewport)
*/
public WidgetsDrawingManager( boolean isEditorMode, int gameResX, int gameResY )
{
this.oneTextureForAllWidgets = isEditorMode;
if ( oneTextureForAllWidgets )
this.textures = new TransformableTexture[] { TransformableTexture.createMainTexture( gameResX, gameResY, false ) };
else
this.textures = new TransformableTexture[] {};
this.widgetsConfig = new WidgetsConfiguration( gameResX, gameResY );
}
}