2016-06-21 21 views
6

Segue un esempio minimale, completo e verificabile basato sul problema che sto incontrando con il rendering del modello WPF, qui stiamo solo restituendo "particelle" distribuite casualmente su un piano 2D arbitrario dove ogni particella ha un colore corrispondente al suo ordine di spawning .Perché gli elementi devono essere continuamente aggiunti alle raccolte di proprietà GeometryModel3D che sono già state calcolate per il rendering in modo che si verifichi?


MainWindow.cs

public partial class MainWindow : Window { 
    // prng for position generation 
    private static Random rng = new Random(); 
    private readonly ComponentManager comp_manager; 
    private List<Color> color_list; 
    // counter for particle no. 
    private int current_particles; 

    public MainWindow() { 
     InitializeComponent(); 
     comp_manager = new ComponentManager(); 
     current_particles = 0; 
     color_list = new List<Color>(); 
    } 
    // computes the colours corresponding to each particle in 
    // order based on a rough temperature-gradient 
    private void ComputeColorList(int total_particles) { 
     for (int i = 0; i < total_particles; ++i) { 
      Color color = new Color(); 
      color.ScA = 1; 
      color.ScR = (float)i/total_particles; 
      color.ScB = 1 - (float)i/total_particles; 
      color.ScG = (i < total_particles/2) ? (float)i/total_particles : (1 - (float)i/total_particles); 
      // populate color_list 
      color_list.Add(color); 
     } 
    } 
    // clear the simulation view and all Children of WorldModels 
    private void Clear() { 
     comp_manager.Clear(); 
     color_list.Clear(); 
     current_particles = 0; 
     // clear Model3DCollection and re-add the ambient light 
     // NOTE: WorldModels is a Model3DGroup declared in MainWindow.xaml 
     WorldModels.Children.Clear(); 
     WorldModels.Children.Add(new AmbientLight(Colors.White)); 
    } 
    private void Generate(int total) { 
     const int min = -75; 
     const int max = 75; 
     // generate particles 
     while (current_particles < total) { 
      int rand_x = rng.Next(min, max); 
      int rand_y = rng.Next(min, max); 
      comp_manager.AddParticleToComponent(new Point3D(rand_x, rand_y, .0), 1.0); 
      Dispatcher.Invoke(() => { comp_manager.Update(); }); 
      ++current_particles; 
     } 
    } 
    // generate_button click handler 
    private void OnGenerateClick(object sender, RoutedEventArgs e) { 
     if (current_particles > 0) Clear(); 
     int n_particles = (int)particles_slider.Value; 
     // pre-compute colours of each particle 
     ComputeColorList(n_particles); 
     // add GeometryModel3D instances for each particle component to WorldModels (defined in the XAML code below) 
     for (int i = 0; i < n_particles; ++i) { 
      WorldModels.Children.Add(comp_manager.CreateComponent(color_list[i])); 
     } 
     // generate particles in separate thread purely to maintain 
     // similarities between this minimal example and the actual code 
     Task.Factory.StartNew(() => Generate(n_particles)); 
    } 
} 

ComponentManager.cs

Questa classe fornisce un oggetto convenienza per la gestione di un List di Component casi in modo tale che le particelle possono essere aggiunti e aggiorna appli a ogni Component nello List.

public class ComponentManager { 
    // also tried using an ObservableCollection<Component> but no difference 
    private readonly List<Component> comp_list; 
    private int id_counter = 0; 
    private int current_counter = -1; 

    public ComponentManager() { 
     comp_list = new List<Component>(); 
    } 
    public Model3D CreateComponent(Color color) { 
     comp_list.Add(new Component(color, ++id_counter)); 
     // get the Model3D of most-recently-added Component and return it 
     return comp_list[comp_list.Count - 1].ComponentModel; 
    } 
    public void AddParticleToComponent(Point3D pos, double size) { 
     comp_list[++current_counter].SpawnParticle(pos, size); 
    } 
    public void Update() { 
     // VERY SLOW, NEED WAY TO CACHE ALREADY RENDERED COMPONENTS 
     foreach (var p in comp_list) { p.Update(); } 
    } 
    public void Clear() { 
     id_counter = 0; 
     current_counter = -1; 
     foreach(var p in comp_list) { p.Clear(); } 
     comp_list.Clear(); 
    } 
} 

Component.cs

Questa classe rappresenta il modello GUI di una singola istanza particella con associato un GeometryModel3D dare le proprietà di rendering della particella (cioè il materiale e quindi colore e rendering target/visivo).

// single particle of systems 
public class Particle { 
    public Point3D position; 
    public double size; 
} 
public class Component { 
    private GeometryModel3D component_model; 
    private Point3DCollection positions; // model Positions collection 
    private Int32Collection triangles; // model TriangleIndices collection 
    private PointCollection textures; // model TextureCoordinates collection 
    private Particle p; 
    private int id; 
    // flag determining if this component has been rendered 
    private bool is_done = false; 

    public Component(Color _color, int _id) { 
     p = null; 
     id = _id; 
     component_model = new GeometryModel3D { Geometry = new MeshGeometry3D() }; 
     Ellipse e = new Ellipse { 
      Width = 32.0, 
      Height = 32.0 
     }; 
     RadialGradientBrush rb = new RadialGradientBrush(); 
     // set colours of the brush such that each particle has own colour 
     rb.GradientStops.Add(new GradientStop(_color, 0.0)); 
     // fade boundary of particle 
     rb.GradientStops.Add(new GradientStop(Colors.Black, 1.0)); 
     rb.Freeze(); 
     e.Fill = rb; 
     e.Measure(new Size(32.0, 32.0)); 
     e.Arrange(new Rect(0.0, 0.0, 32.0, 32.0)); 
     // cached for increased performance 
     e.CacheMode = new BitmapCache(); 
     BitmapCacheBrush bcb = new BitmapCacheBrush(e); 
     DiffuseMaterial dm = new DiffuseMaterial(bcb); 
     component_model.Material = dm; 
     positions = new Point3DCollection(); 
     triangles = new Int32Collection(); 
     textures = new PointCollection(); 
     ((MeshGeometry3D)component_model.Geometry).Positions = positions; 
     ((MeshGeometry3D)component_model.Geometry).TextureCoordinates = textures; 
     ((MeshGeometry3D)component_model.Geometry).TriangleIndices = triangles; 
    } 
    public Model3D ComponentModel => component_model; 
    public void Update() { 
     if (p == null) return; 
     if (!is_done) { 
      int pos_index = id * 4; 
      // compute positions 
      positions.Add(new Point3D(p.position.X, p.position.Y, p.position.Z)); 
      positions.Add(new Point3D(p.position.X, p.position.Y + p.size, p.position.Z)); 
      positions.Add(new Point3D(p.position.X + p.size, p.position.Y + p.size, p.position.Z)); 
      positions.Add(new Point3D(p.position.X + p.size, p.position.Y, p.position.Z)); 
      // compute texture co-ordinates 
      textures.Add(new Point(0.0, 0.0)); 
      textures.Add(new Point(0.0, 1.0)); 
      textures.Add(new Point(1.0, 1.0)); 
      textures.Add(new Point(1.0, 0.0)); 
      // compute triangle indices 
      triangles.Add(pos_index); 
      triangles.Add(pos_index+2); 
      triangles.Add(pos_index+1); 
      triangles.Add(pos_index); 
      triangles.Add(pos_index+3); 
      triangles.Add(pos_index+2); 
      // commenting out line below enables rendering of components but v. slow 
      // due to continually filling up above collections 
      is_done = true; 
     } 
    } 
    public void SpawnParticle(Point3D _pos, double _size) { 
     p = new Particle { 
      position = _pos, 
      size = _size 
     }; 
    } 
    public void Clear() { 
     ((MeshGeometry3D)component_model.Geometry).Positions.Clear(); 
     ((MeshGeometry3D)component_model.Geometry).TextureCoordinates.Clear(); 
     ((MeshGeometry3D)component_model.Geometry).TriangleIndices.Clear(); 
    } 
} 

MainWindow.xaml

Il codice (greggio) XAML solo per completezza nel caso in cui qualcuno vuole verificare questo esempio.

<Window x:Class="GraphicsTestingWPF.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:local="clr-namespace:GraphicsTestingWPF" 
    mc:Ignorable="d" 
    Title="MainWindow" Height="768" Width="1366"> 
<Grid> 
    <Grid Background="Black" Visibility="Visible" Width ="Auto" Height="Auto" Margin="5,3,623,10" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> 
     <Viewport3D Name="World" Focusable="True"> 
      <Viewport3D.Camera> 
       <OrthographicCamera x:Name="orthograghic_camera" Position="0,0,32" LookDirection="0,0,-32" UpDirection="0,1,0" Width="256"/> 
      </Viewport3D.Camera> 

      <Viewport3D.Children> 
       <ModelVisual3D> 
        <ModelVisual3D.Content> 
         <Model3DGroup x:Name="WorldModels"> 
          <AmbientLight Color="#FFFFFFFF" /> 
         </Model3DGroup> 
        </ModelVisual3D.Content> 
       </ModelVisual3D> 
      </Viewport3D.Children> 
     </Viewport3D> 
    </Grid> 
    <Slider Maximum="1000" TickPlacement="BottomRight" TickFrequency="50" IsSnapToTickEnabled="True" x:Name="particles_slider" Margin="0,33,130,0" VerticalAlignment="Top" Height="25" HorizontalAlignment="Right" Width="337"/> 
    <Label x:Name="NParticles_Label" Content="Number of Particles" Margin="0,29,472,0" VerticalAlignment="Top" RenderTransformOrigin="1.019,-0.647" HorizontalAlignment="Right" Width="123"/> 
    <TextBox Text="{Binding ElementName=particles_slider, Path=Value, UpdateSourceTrigger=PropertyChanged}" x:Name="particle_val" Height="23" Margin="0,32,85,0" TextWrapping="Wrap" VerticalAlignment="Top" TextAlignment="Right" HorizontalAlignment="Right" Width="40"/> 
    <Button x:Name="generate_button" Content="Generate" Margin="0,86,520,0" VerticalAlignment="Top" Click="OnGenerateClick" HorizontalAlignment="Right" Width="75"/> 
</Grid> 
</Window> 

Problema

Come avrete ipotizzato dal codice, il problema risiede nelle Update metodi di ComponentManager e Component. Affinché il rendering abbia successo devo aggiornare ogni Component ogni volta che una particella viene aggiunta al sistema di particelle - Ho cercato di mitigare qualsiasi problema di prestazioni da questo usando la flag is_done nella classe Component, da impostare true quando le proprietà delle particelle (positions, textures e triangles) sono state calcolate la prima volta. Quindi, o almeno così pensavo, in ogni chiamata successiva a Component::Update() per un componente, sarebbero stati utilizzati i valori calcolati in precedenza di queste raccolte.

Tuttavia, questo non funziona in questo modo poiché l'impostazione di is_done su true come spiegato sopra non causerà semplicemente il rendering. Se commento fuori is_done = true; allora tutto è reso comunque è incredibilmente lento - molto probabilmente a causa dell'enorme numero di elementi che vengono aggiunti alle collezioni positions ecc. Di ogni Component (l'utilizzo della memoria esplode come mostrato dalla diagnostica del debugger).


Domanda

Perché devo continuare ad aggiungere elementi precedentemente calcolati a queste collezioni per il rendering a verificarsi?

In altre parole, perché non basta prendere la già calcolato Positions, TextureCoordinates e TriangleIndices da ogni Component e utilizzare questi durante il rendering?

+0

Ti manca il tag della finestra di chiusura in XAML. Dove viene dichiarato WorldModels? –

+0

È tua intenzione che ogni volta che viene generato un clic vengano aggiunte particelle aggiuntive alla raccolta, perché attualmente Clear() viene chiamato ogni volta che ricrea la tua raccolta? –

+0

@DamianGreen Correggerò che, 'WorldModels' è dichiarato nel codice XAML come' Model3DGroup'. Inoltre, non è necessario che ciascun clic del pulsante di generazione crei un singolo sistema di particelle: qualsiasi clic successivo dovrebbe cancellare il precedente e crearne uno nuovo; questo è simile a come funziona il codice effettivo da cui è derivato questo problema. – ArchbishopOfBanterbury

risposta

4

Sembra che potrebbero esserci diversi problemi qui.

Il primo che ho trovato è che stai chiamando comp_mgr.Update()ogni volta che aggiungi una particella. Questo, a sua volta, chiama Update() su ogni particella. Tutto ciò si traduce in un'operazione O (n^2) che significa che per 200 particelle (il tuo min), stai eseguendo la logica di aggiornamento dei componenti 40.000 volte. Questo è sicuramente ciò che sta causando a essere lento.

Per eliminare questo, ho rimosso la chiamata comp_mgr.Update() dal ciclo while. Ma poi non ho ottenuto alcun punto, proprio come quando disattivi la riga is_done = true;.

È interessante notare che quando ho aggiunto una seconda chiamata a comp_mgr.Update(), ho ottenuto un singolo punto. E con le chiamate successive ho ottenuto un punto aggiuntivo per ogni chiamata. Ciò implica che, anche con il codice più lento, ottieni solo 199 punti nell'impostazione a 200 punti.

Sembra esserci un problema più profondo da qualche parte, ma non riesco a trovarlo. Aggiornerò se lo faccio Forse questo condurrà te o qualcun altro alla risposta.

Per ora, il metodo MainWindow.Generate() aspetto:

private void Generate(int _total) 
{ 
    const int min = -75; 
    const int max = 75; 
    // generate particles 
    while (current_particles < _total) 
    { 
     int rand_x = rng.Next(min, max); 
     int rand_y = rng.Next(min, max); 
     comp_manager.AddParticleToComponent(new Point3D(rand_x, rand_y, .0), 1.0); 
     ++current_particles; 
    } 
    Dispatcher.Invoke(() => { comp_manager.Update(); }); 
} 

dove replicare l'Update() chiamata n volte risultati in n -1 punti che sono resi.

+0

Vedo, questo andrebbe in qualche modo a spiegare i problemi di prestazioni. Il problema è, tuttavia, che nel codice effettivo da cui deriva questo problema minimo il codice eseguito nel ciclo 'while' è codice C++ nativo, eseguito in un thread separato, che è molto intensivo dal punto di vista computazionale - quindi per ottenere un tempo reale simulazione Non posso semplicemente aggiornare il modello/GUI dopo che il ciclo ha terminato l'esecuzione. – ArchbishopOfBanterbury

+0

Con il solo spostamento della chiamata 'comp_manager.Update()', non vedo alcuna particella renderizzata.Commentate anche la riga 'is_done' in' Component :: Render'? – Zack

+0

ma per chiarire che non si desidera eseguire la funzione di aggiornamento delle particelle nel ciclo while ancora? @gregsdennis fa un buon punto –

Problemi correlati